[automerge] radio: provide hardcoded IMEI and null IMEI-SV 2p: 5154019c1f am: fa19a971a2 am: 0c87c389f4
Original change: https://googleplex-android-review.googlesource.com/c/device/google/cuttlefish/+/18620247
Change-Id: Ia845081caaef3a81cb6912564f1de80ddeb9a692
Signed-off-by: Automerger Merge Worker <[email protected]>
diff --git a/.clang-format b/.clang-format
index 9f9ffe3..a03f8b3 100644
--- a/.clang-format
+++ b/.clang-format
@@ -19,3 +19,4 @@
# of the below options.
BasedOnStyle: Google
+IncludeBlocks: Preserve
diff --git a/Android.mk b/Android.mk
index 59e9806..78b607f 100644
--- a/Android.mk
+++ b/Android.mk
@@ -11,7 +11,41 @@
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
-ifneq ($(filter vsoc_arm64 vsoc_x86 vsoc_x86_64, $(TARGET_BOARD_PLATFORM)),)
+
+$(eval $(call declare-copy-files-license-metadata,device/google/cuttlefish,.idc,SPDX-license-identifier-Apache-2.0,notice,build/soong/licenses/LICENSE,))
+$(eval $(call declare-copy-files-license-metadata,device/google/cuttlefish,default-permissions.xml,SPDX-license-identifier-Apache-2.0,notice,build/soong/licenses/LICENSE,))
+$(eval $(call declare-copy-files-license-metadata,device/google/cuttlefish,libnfc-nci.conf,SPDX-license-identifier-Apache-2.0,notice,build/soong/licenses/LICENSE,))
+$(eval $(call declare-copy-files-license-metadata,device/google/cuttlefish,fstab.postinstall,SPDX-license-identifier-Apache-2.0,notice,build/soong/licenses/LICENSE,))
+$(eval $(call declare-copy-files-license-metadata,device/google/cuttlefish,ueventd.rc,SPDX-license-identifier-Apache-2.0,notice,build/soong/licenses/LICENSE,))
+$(eval $(call declare-copy-files-license-metadata,device/google/cuttlefish,wpa_supplicant.conf,SPDX-license-identifier-Apache-2.0,notice,build/soong/licenses/LICENSE,))
+$(eval $(call declare-copy-files-license-metadata,device/google/cuttlefish,hals.conf,SPDX-license-identifier-Apache-2.0,notice,build/soong/licenses/LICENSE,))
+$(eval $(call declare-copy-files-license-metadata,device/google/cuttlefish,device_state_configuration.xml,SPDX-license-identifier-Apache-2.0,notice,build/soong/licenses/LICENSE,))
+$(eval $(call declare-copy-files-license-metadata,device/google/cuttlefish,task_profiles.json,SPDX-license-identifier-Apache-2.0,notice,build/soong/licenses/LICENSE,))
+$(eval $(call declare-copy-files-license-metadata,device/google/cuttlefish,p2p_supplicant.conf,SPDX-license-identifier-Apache-2.0,notice,build/soong/licenses/LICENSE,))
+$(eval $(call declare-copy-files-license-metadata,device/google/cuttlefish,wpa_supplicant.conf,SPDX-license-identifier-Apache-2.0,notice,build/soong/licenses/LICENSE,))
+$(eval $(call declare-copy-files-license-metadata,device/google/cuttlefish,wpa_supplicant_overlay.conf,SPDX-license-identifier-Apache-2.0,notice,build/soong/licenses/LICENSE,))
+$(eval $(call declare-copy-files-license-metadata,device/google/cuttlefish,wpa_supplicant.rc,SPDX-license-identifier-Apache-2.0,notice,build/soong/licenses/LICENSE,))
+$(eval $(call declare-copy-files-license-metadata,device/google/cuttlefish,init.cutf_cvm.rc,SPDX-license-identifier-Apache-2.0,notice,build/soong/licenses/LICENSE,))
+$(eval $(call declare-copy-files-license-metadata,device/google/cuttlefish,bt_vhci_forwarder.rc,SPDX-license-identifier-Apache-2.0,notice,build/soong/licenses/LICENSE,))
+$(eval $(call declare-copy-files-license-metadata,device/google/cuttlefish,fstab.f2fs,SPDX-license-identifier-Apache-2.0,notice,build/soong/licenses/LICENSE,))
+$(eval $(call declare-copy-files-license-metadata,device/google/cuttlefish,fstab.ext4,SPDX-license-identifier-Apache-2.0,notice,build/soong/licenses/LICENSE,))
+$(eval $(call declare-copy-files-license-metadata,device/google/cuttlefish,init.rc,SPDX-license-identifier-Apache-2.0,notice,build/soong/licenses/LICENSE,))
+$(eval $(call declare-copy-files-license-metadata,device/google/cuttlefish,audio_policy.conf,SPDX-license-identifier-Apache-2.0,notice,build/soong/licenses/LICENSE,))
+
+$(eval $(call declare-copy-files-license-metadata,device/google/cuttlefish/shared/config,pci.ids,SPDX-license-identifier-BSD-3-Clause,notice,device/google/cuttlefish/shared/config/LICENSE_BSD,))
+
+$(eval $(call declare-1p-copy-files,device/google/cuttlefish,privapp-permissions-cuttlefish.xml))
+$(eval $(call declare-1p-copy-files,device/google/cuttlefish,media_profiles_V1_0.xml))
+$(eval $(call declare-1p-copy-files,device/google/cuttlefish,media_codecs_performance.xml))
+$(eval $(call declare-1p-copy-files,device/google/cuttlefish,cuttlefish_excluded_hardware.xml))
+$(eval $(call declare-1p-copy-files,device/google/cuttlefish,media_codecs.xml))
+$(eval $(call declare-1p-copy-files,device/google/cuttlefish,media_codecs_google_video.xml))
+$(eval $(call declare-1p-copy-files,device/google/cuttlefish,car_audio_configuration.xml))
+$(eval $(call declare-1p-copy-files,device/google/cuttlefish,audio_policy_configuration.xml))
+$(eval $(call declare-1p-copy-files,device/google/cuttlefish,preinstalled-packages-product-car-cuttlefish.xml))
+$(eval $(call declare-1p-copy-files,hardware/google/camera/devices,.json))
+
+ifneq ($(filter vsoc_arm vsoc_arm64 vsoc_x86 vsoc_x86_64, $(TARGET_BOARD_PLATFORM)),)
LOCAL_PATH:= $(call my-dir)
include $(CLEAR_VARS)
diff --git a/AndroidProducts.mk b/AndroidProducts.mk
index 8d06ed7..d345316 100644
--- a/AndroidProducts.mk
+++ b/AndroidProducts.mk
@@ -16,24 +16,26 @@
PRODUCT_MAKEFILES := \
aosp_cf_arm_only_phone:$(LOCAL_DIR)/vsoc_arm_only/phone/aosp_cf.mk \
- aosp_cf_arm64_auto:$(LOCAL_DIR)/vsoc_arm64/auto/aosp_cf.mk \
+ aosp_cf_arm64_auto:$(LOCAL_DIR)/vsoc_arm64_only/auto/aosp_cf.mk \
aosp_cf_arm64_phone:$(LOCAL_DIR)/vsoc_arm64/phone/aosp_cf.mk \
+ aosp_cf_arm64_phone_hwasan:$(LOCAL_DIR)/vsoc_arm64/phone/aosp_cf_hwasan.mk \
aosp_cf_arm64_only_phone:$(LOCAL_DIR)/vsoc_arm64_only/phone/aosp_cf.mk \
- aosp_cf_x86_64_auto:$(LOCAL_DIR)/vsoc_x86_64/auto/device.mk \
+ aosp_cf_arm64_only_phone_hwasan:$(LOCAL_DIR)/vsoc_arm64_only/phone/aosp_cf_hwasan.mk \
+ aosp_cf_arm64_slim:$(LOCAL_DIR)/vsoc_arm64_only/slim/aosp_cf.mk \
+ aosp_cf_x86_64_auto:$(LOCAL_DIR)/vsoc_x86_64/auto/aosp_cf.mk \
aosp_cf_x86_64_pc:$(LOCAL_DIR)/vsoc_x86_64/pc/aosp_cf.mk \
aosp_cf_x86_64_phone:$(LOCAL_DIR)/vsoc_x86_64/phone/aosp_cf.mk \
- aosp_cf_x86_64_tv:$(LOCAL_DIR)/vsoc_x86_64/tv/device.mk \
+ aosp_cf_x86_64_tv:$(LOCAL_DIR)/vsoc_x86_64/tv/aosp_cf.mk \
aosp_cf_x86_64_foldable:$(LOCAL_DIR)/vsoc_x86_64/phone/aosp_cf_foldable.mk \
aosp_cf_x86_64_only_phone:$(LOCAL_DIR)/vsoc_x86_64_only/phone/aosp_cf.mk \
- aosp_cf_x86_auto:$(LOCAL_DIR)/vsoc_x86/auto/device.mk \
+ aosp_cf_x86_64_slim:$(LOCAL_DIR)/vsoc_x86_64_only/slim/aosp_cf.mk \
+ aosp_cf_x86_auto:$(LOCAL_DIR)/vsoc_x86/auto/aosp_cf.mk \
aosp_cf_x86_pasan:$(LOCAL_DIR)/vsoc_x86/pasan/aosp_cf.mk \
aosp_cf_x86_phone:$(LOCAL_DIR)/vsoc_x86/phone/aosp_cf.mk \
- aosp_cf_x86_phone_noapex:$(LOCAL_DIR)/vsoc_x86_noapex/aosp_cf_noapex.mk \
aosp_cf_x86_only_phone:$(LOCAL_DIR)/vsoc_x86_only/phone/aosp_cf.mk \
- aosp_cf_x86_go_phone:$(LOCAL_DIR)/vsoc_x86/go_phone/device.mk \
- aosp_cf_x86_go_512_phone:$(LOCAL_DIR)/vsoc_x86/go_512_phone/device.mk \
- aosp_cf_x86_tv:$(LOCAL_DIR)/vsoc_x86/tv/device.mk
-
+ aosp_cf_x86_go_phone:$(LOCAL_DIR)/vsoc_x86/go/aosp_cf.mk \
+ aosp_cf_x86_tv:$(LOCAL_DIR)/vsoc_x86/tv/aosp_cf.mk \
+ aosp_cf_x86_wear:$(LOCAL_DIR)/vsoc_x86/wear/aosp_cf.mk \
COMMON_LUNCH_CHOICES := \
aosp_cf_arm64_auto-userdebug \
diff --git a/CleanSpec.mk b/CleanSpec.mk
index f235841..6d10d12 100644
--- a/CleanSpec.mk
+++ b/CleanSpec.mk
@@ -58,6 +58,9 @@
$(call add-clean-step, rm -rf $(PRODUCT_OUT)/vendor/etc/init/[email protected])
$(call add-clean-step, rm -rf $(PRODUCT_OUT)/vendor/etc/init/[email protected])
+$(call add-clean-step, rm -rf $(PRODUCT_OUT)/vendor/etc/init/[email protected])
+$(call add-clean-step, rm -rf $(PRODUCT_OUT)/vendor/etc/init/[email protected])
+
$(call add-clean-step, rm -rf $(PRODUCT_OUT)/vendor/etc/init/[email protected])
$(call add-clean-step, rm -rf $(PRODUCT_OUT)/vendor/bin/hw/[email protected])
@@ -68,3 +71,8 @@
$(call add-clean-step, rm -rf $(PRODUCT_OUT)/vendor/etc/vintf/manifest/[email protected])
$(call add-clean-step, rm -rf $(PRODUCT_OUT)/product/apex/com.android.gki.*)
+
+$(call add-clean-step, find $(PRODUCT_OUT)/system -type f -name "*charger*" -print0 | xargs -0 rm -f)
+$(call add-clean-step, find $(PRODUCT_OUT)/vendor -type f -name "*health@*" -print0 | xargs -0 rm -f)
+$(call add-clean-step, find $(PRODUCT_OUT)/recovery/root -type f -name "*charger*" -print0 | xargs -0 rm -f)
+$(call add-clean-step, find $(PRODUCT_OUT)/recovery/root -type f -name "*health@*" -print0 | xargs -0 rm -f)
diff --git a/README.md b/README.md
index 2220898..61c9987 100644
--- a/README.md
+++ b/README.md
@@ -5,22 +5,33 @@
1. Make sure virtualization with KVM is available.
```bash
- grep -c -w "vmx\|svm" /proc/cpuinfo
- ```
+ grep -c -w "vmx\|svm" /proc/cpuinfo
+ ```
This should return a non-zero value. If running on a cloud machine, this may
take cloud-vendor-specific steps to enable. For Google Compute Engine
specifically, see the [GCE guide].
- [GCE guide]: https://cloud.google.com/compute/docs/instances/enable-nested-virtualization-vm-instances
+ [GCE guide]: https://cloud.google.com/compute/docs/instances/enable-nested-virtualization-vm-instances
+
+*** promo
+ ARM specific steps:
+ - When running on an ARM machine, the most direct way is to check
+ for the existence of `/dev/kvm`. Note that this method can also be used to
+ confirm support of KVM on any environment.
+ - Before proceeding to the next step, please first follow
+ [the guide](multiarch-howto.md) to adjust APT sources.
+***
2. Download, build, and install the host debian package:
```bash
+ sudo apt install -y git devscripts config-package-dev debhelper-compat golang
git clone https://github.com/google/android-cuttlefish
cd android-cuttlefish
- debuild -i -us -uc -b
- sudo dpkg -i ../cuttlefish-common_*_amd64.deb || sudo apt-get install -f
+ debuild -i -us -uc -b -d
+ sudo dpkg -i ../cuttlefish-common_*_*64.deb || sudo apt-get install -f
+ sudo usermod -aG kvm,cvdnetwork,render $USER
sudo reboot
```
@@ -31,6 +42,11 @@
4. Enter a branch name. Start with `aosp-master` if you don't know what you're
looking for
5. Navigate to `aosp_cf_x86_64_phone` and click on `userdebug` for the latest build
+
+*** promo
+ For ARM, use branch `aosp-master-throttled-copped` and device target `aosp_cf_arm64_only_phone-userdebug`
+***
+
6. Click on `Artifacts`
7. Scroll down to the OTA images. These packages look like
`aosp_cf_x86_64_phone-img-xxxxxx.zip` -- it will always have `img` in the name.
@@ -50,10 +66,6 @@
`$ HOME=$PWD ./bin/launch_cvd`
-11. Stop cuttlefish with:
-
- `$ HOME=$PWD ./bin/stop_cvd`
-
## Debug Cuttlefish
You can use `adb` to debug it, just like a physical device:
@@ -67,12 +79,10 @@
WebRTC on Cuttlefish
[documentation](https://source.android.com/setup/create/cuttlefish-ref-webrtc).
-## Launch Viewer (VNC)
+## Stop Cuttlefish
-When launching with `--start_vnc_server=true` , You can use the
-[TightVNC JViewer](https://www.tightvnc.com/download.php). Once you have
-downloaded the *TightVNC Java Viewer JAR in a ZIP archive*, run it with
+You will need to stop the virtual device within the same directory as you used
+to launch the device.
- `$ java -jar tightvnc-jviewer.jar -ScalingFactor=50 -Tunneling=no -host=localhost -port=6444`
+ `$ HOME=$PWD ./bin/stop_cvd`
-Click "Connect" and you should see a lock screen!
diff --git a/TEST_MAPPING b/TEST_MAPPING
index 4067d27..92e1712 100644
--- a/TEST_MAPPING
+++ b/TEST_MAPPING
@@ -19,6 +19,15 @@
},
{
"name": "vts_ibase_test"
+ },
+ {
+ "name": "OverlayDeviceTests"
+ },
+ {
+ "name": "CtsNativeVerifiedBootTestCases"
+ },
+ {
+ "name": "CtsScopedStorageDeviceOnlyTest"
}
]
}
diff --git a/apex/com.google.aosp_cf_phone.hardware.core_permissions/Android.bp b/apex/com.google.aosp_cf_phone.hardware.core_permissions/Android.bp
new file mode 100644
index 0000000..2900f32
--- /dev/null
+++ b/apex/com.google.aosp_cf_phone.hardware.core_permissions/Android.bp
@@ -0,0 +1,44 @@
+// Copyright (C) 2021 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package {
+ default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+override_apex {
+ name: "com.google.aosp_cf_phone.hardware.core_permissions",
+ base: "com.android.hardware.core_permissions",
+ prebuilts: [
+ "android.hardware.audio.low_latency.prebuilt.xml",
+ "android.hardware.biometrics.face.prebuilt.xml",
+ "android.hardware.ethernet.prebuilt.xml",
+ "android.hardware.faketouch.prebuilt.xml",
+ "android.hardware.fingerprint.prebuilt.xml",
+ "android.hardware.location.gps.prebuilt.xml",
+ "android.hardware.reboot_escrow.prebuilt.xml",
+ "android.hardware.usb.accessory.prebuilt.xml",
+ "android.hardware.usb.host.prebuilt.xml",
+ "android.hardware.vulkan.level-0.prebuilt.xml",
+ "android.hardware.vulkan.version-1_0_3.prebuilt.xml",
+ "android.software.device_id_attestation.prebuilt.xml",
+ "android.software.ipsec_tunnels.prebuilt.xml",
+ "android.software.opengles.deqp.level-2022-03-01.prebuilt.xml",
+ "android.software.sip.voip.prebuilt.xml",
+ "android.software.verified_boot.prebuilt.xml",
+ "android.software.vulkan.deqp.level-2022-03-01.prebuilt.xml",
+ "aosp_excluded_hardware.prebuilt.xml",
+ "cuttlefish_excluded_hardware.prebuilt.xml",
+ "handheld_core_hardware.prebuilt.xml",
+ ],
+}
diff --git a/apex/com.google.aosp_cf_phone.rros/Android.bp b/apex/com.google.aosp_cf_phone.rros/Android.bp
new file mode 100644
index 0000000..325e1b3
--- /dev/null
+++ b/apex/com.google.aosp_cf_phone.rros/Android.bp
@@ -0,0 +1,35 @@
+// Copyright (C) 2021 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package {
+ default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+apex {
+ name: "com.google.aosp_cf_phone.rros",
+ manifest: "apex_manifest.json",
+ key: "com.google.cf.apex.key",
+ certificate: ":com.google.cf.apex.certificate",
+ file_contexts: "file_contexts",
+ use_vndk_as_stable: true,
+ updatable: false,
+ // Install the apex in /vendor/apex
+ soc_specific: true,
+ rros: [
+ "cuttlefish_overlay_connectivity",
+ "cuttlefish_overlay_frameworks_base_core",
+ "cuttlefish_overlay_settings_provider",
+ "cuttlefish_phone_overlay_frameworks_base_core",
+ ],
+}
diff --git a/apex/com.google.aosp_cf_phone.rros/apex_manifest.json b/apex/com.google.aosp_cf_phone.rros/apex_manifest.json
new file mode 100644
index 0000000..e5ebe27
--- /dev/null
+++ b/apex/com.google.aosp_cf_phone.rros/apex_manifest.json
@@ -0,0 +1,4 @@
+{
+ "name": "com.google.aosp_cf_phone.rros",
+ "version": 1
+}
diff --git a/apex/com.google.aosp_cf_phone.rros/file_contexts b/apex/com.google.aosp_cf_phone.rros/file_contexts
new file mode 100644
index 0000000..cb7fd8d
--- /dev/null
+++ b/apex/com.google.aosp_cf_phone.rros/file_contexts
@@ -0,0 +1,2 @@
+(/.*)? u:object_r:vendor_file:s0
+/overlay(/.*)? u:object_r:vendor_overlay_file:s0
diff --git a/apex/com.google.aosp_cf_slim.hardware.core_permissions/Android.bp b/apex/com.google.aosp_cf_slim.hardware.core_permissions/Android.bp
new file mode 100644
index 0000000..959c950
--- /dev/null
+++ b/apex/com.google.aosp_cf_slim.hardware.core_permissions/Android.bp
@@ -0,0 +1,45 @@
+// Copyright (C) 2022 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package {
+ default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+override_apex {
+ name: "com.google.aosp_cf_slim.hardware.core_permissions",
+ base: "com.android.hardware.core_permissions",
+ prebuilts: [
+ "android.hardware.audio.low_latency.prebuilt.xml",
+ "android.hardware.biometrics.face.prebuilt.xml",
+ "android.hardware.ethernet.prebuilt.xml",
+ "android.hardware.faketouch.prebuilt.xml",
+ "android.hardware.fingerprint.prebuilt.xml",
+ "android.hardware.location.gps.prebuilt.xml",
+ "android.hardware.reboot_escrow.prebuilt.xml",
+ "android.hardware.usb.accessory.prebuilt.xml",
+ "android.hardware.usb.host.prebuilt.xml",
+ "android.hardware.vulkan.level-0.prebuilt.xml",
+ "android.hardware.vulkan.version-1_0_3.prebuilt.xml",
+ "android.software.device_id_attestation.prebuilt.xml",
+ "android.software.ipsec_tunnels.prebuilt.xml",
+ "android.software.opengles.deqp.level-2022-03-01.prebuilt.xml",
+ "android.software.sip.voip.prebuilt.xml",
+ "android.software.verified_boot.prebuilt.xml",
+ "android.software.vulkan.deqp.level-2022-03-01.prebuilt.xml",
+ "aosp_excluded_hardware.prebuilt.xml",
+ "cuttlefish_excluded_hardware.prebuilt.xml",
+ "handheld_core_hardware.prebuilt.xml",
+ "slim_excluded_hardware.prebuilt.xml",
+ ],
+}
diff --git a/apex/com.google.aosp_cf_slim.rros/Android.bp b/apex/com.google.aosp_cf_slim.rros/Android.bp
new file mode 100644
index 0000000..1a98d1c
--- /dev/null
+++ b/apex/com.google.aosp_cf_slim.rros/Android.bp
@@ -0,0 +1,38 @@
+// Copyright (C) 2022 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+soong_namespace {
+ imports: [
+ "device/generic/goldfish",
+ ],
+}
+
+package {
+ default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+apex {
+ name: "com.google.aosp_cf_slim.rros",
+ manifest: "apex_manifest.json",
+ key: "com.google.cf.apex.key",
+ certificate: ":com.google.cf.apex.certificate",
+ file_contexts: "file_contexts",
+ use_vndk_as_stable: true,
+ updatable: false,
+ // Install the apex in /vendor/apex
+ soc_specific: true,
+ rros: [
+ "slim_overlay_frameworks_base_core",
+ ],
+}
diff --git a/apex/com.google.aosp_cf_slim.rros/apex_manifest.json b/apex/com.google.aosp_cf_slim.rros/apex_manifest.json
new file mode 100644
index 0000000..26c7bd5
--- /dev/null
+++ b/apex/com.google.aosp_cf_slim.rros/apex_manifest.json
@@ -0,0 +1,4 @@
+{
+ "name": "com.google.aosp_cf_slim.rros",
+ "version": 1
+}
diff --git a/apex/com.google.aosp_cf_slim.rros/file_contexts b/apex/com.google.aosp_cf_slim.rros/file_contexts
new file mode 100644
index 0000000..cb7fd8d
--- /dev/null
+++ b/apex/com.google.aosp_cf_slim.rros/file_contexts
@@ -0,0 +1,2 @@
+(/.*)? u:object_r:vendor_file:s0
+/overlay(/.*)? u:object_r:vendor_overlay_file:s0
diff --git a/apex/com.google.cf.bt/Android.bp b/apex/com.google.cf.bt/Android.bp
new file mode 100644
index 0000000..438db02
--- /dev/null
+++ b/apex/com.google.cf.bt/Android.bp
@@ -0,0 +1,45 @@
+// Copyright (C) 2021 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package {
+ default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+prebuilt_etc {
+ name: "com.google.cf.bt.rc",
+ src: "com.google.cf.bt.rc",
+ installable: false,
+}
+
+apex {
+ name: "com.google.cf.bt",
+ manifest: "manifest.json",
+ file_contexts: "file_contexts",
+ key: "com.google.cf.apex.key",
+ certificate: ":com.google.cf.apex.certificate",
+ use_vndk_as_stable: true,
+ updatable: false,
+ soc_specific: true,
+ binaries: [
+ "[email protected]",
+ "bt_vhci_forwarder",
+ ],
+ prebuilts: [
+ "android.hardware.bluetooth_le.prebuilt.xml",
+ "android.hardware.bluetooth.prebuilt.xml",
+ "com.google.cf.bt.rc",
+ ],
+ init_rc: ["com.google.cf.bt.trig.rc"],
+ vintf_fragments: [":[email protected]"],
+}
diff --git a/apex/com.google.cf.bt/com.google.cf.bt.rc b/apex/com.google.cf.bt/com.google.cf.bt.rc
new file mode 100644
index 0000000..a5f2ae7
--- /dev/null
+++ b/apex/com.google.cf.bt/com.google.cf.bt.rc
@@ -0,0 +1,9 @@
+service bt_vhci_forwarder /apex/com.google.cf.bt/bin/bt_vhci_forwarder -virtio_console_dev=${vendor.ser.bt-uart}
+ user bluetooth
+ group bluetooth
+
+service btlinux-1.1 /apex/com.google.cf.bt/bin/hw/[email protected]
+ class hal
+ user bluetooth
+ group bluetooth net_admin net_bt_admin
+ capabilities NET_ADMIN
diff --git a/apex/com.google.cf.bt/com.google.cf.bt.trig.rc b/apex/com.google.cf.bt/com.google.cf.bt.trig.rc
new file mode 100644
index 0000000..a082c18
--- /dev/null
+++ b/apex/com.google.cf.bt/com.google.cf.bt.trig.rc
@@ -0,0 +1,6 @@
+## Init files within the APEX do not support triggers (b/202731768)
+## By adding this as an init_rc parameter of the APEX the file will be installed
+## outside of the APEX and instead be installed under /vendor/etc/init.
+on post-fs-data
+ start bt_vhci_forwarder
+
diff --git a/apex/com.google.cf.bt/file_contexts b/apex/com.google.cf.bt/file_contexts
new file mode 100644
index 0000000..b148753
--- /dev/null
+++ b/apex/com.google.cf.bt/file_contexts
@@ -0,0 +1,4 @@
+(/.*)? u:object_r:vendor_file:s0
+/bin/hw/[email protected] u:object_r:hal_bluetooth_btlinux_exec:s0
+/bin/bt_vhci_forwarder u:object_r:bt_vhci_forwarder_exec:s0
+/etc/permissions(/.*)? u:object_r:vendor_configs_file:s0
\ No newline at end of file
diff --git a/apex/com.google.cf.bt/manifest.json b/apex/com.google.cf.bt/manifest.json
new file mode 100644
index 0000000..0436efe
--- /dev/null
+++ b/apex/com.google.cf.bt/manifest.json
@@ -0,0 +1,4 @@
+{
+ "name": "com.google.cf.bt",
+ "version": 1
+}
diff --git a/apex/com.google.cf.input.config/Android.bp b/apex/com.google.cf.input.config/Android.bp
new file mode 100644
index 0000000..00edee9
--- /dev/null
+++ b/apex/com.google.cf.input.config/Android.bp
@@ -0,0 +1,37 @@
+// Copyright (C) 2021 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package {
+ default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+apex {
+ name: "com.google.cf.input.config",
+ // InputDevice expects input config files in /apex/com.android.input.config/etc
+ apex_name: "com.android.input.config",
+ manifest: "apex_manifest.json",
+ key: "com.google.cf.apex.key",
+ certificate: ":com.google.cf.apex.certificate",
+ file_contexts: "file_contexts",
+ use_vndk_as_stable: true,
+ updatable: false,
+ // Install the apex in /vendor/apex
+ soc_specific: true,
+ prebuilts: [
+ "Crosvm_Virtio_Multitouch_Touchscreen_0.idc",
+ "Crosvm_Virtio_Multitouch_Touchscreen_1.idc",
+ "Crosvm_Virtio_Multitouch_Touchscreen_2.idc",
+ "Crosvm_Virtio_Multitouch_Touchscreen_3.idc",
+ ],
+}
diff --git a/apex/com.google.cf.input.config/apex_manifest.json b/apex/com.google.cf.input.config/apex_manifest.json
new file mode 100644
index 0000000..dce7ad4
--- /dev/null
+++ b/apex/com.google.cf.input.config/apex_manifest.json
@@ -0,0 +1,4 @@
+{
+ "name": "com.android.input.config",
+ "version": 1
+}
diff --git a/apex/com.google.cf.input.config/file_contexts b/apex/com.google.cf.input.config/file_contexts
new file mode 100644
index 0000000..e982bd5
--- /dev/null
+++ b/apex/com.google.cf.input.config/file_contexts
@@ -0,0 +1,4 @@
+(/.*)? u:object_r:vendor_file:s0
+/etc/usr/keylayout(/.*)?\.kl u:object_r:vendor_keylayout_file:s0
+/etc/usr/keychars(/.*)?\.kcm u:object_r:vendor_keychars_file:s0
+/etc/usr/idc(/.*)?\.idc u:object_r:vendor_idc_file:s0
diff --git a/apex/com.google.cf.rild/Android.bp b/apex/com.google.cf.rild/Android.bp
new file mode 100644
index 0000000..9e237b5
--- /dev/null
+++ b/apex/com.google.cf.rild/Android.bp
@@ -0,0 +1,59 @@
+// Copyright (C) 2021 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package {
+ default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+prebuilt_etc {
+ name: "com.google.cf.rild.rc",
+ src: "com.google.cf.rild.rc",
+ installable: false,
+}
+
+prebuilt_etc {
+ name: "ld.config.txt",
+ src: "ld.config.txt",
+ installable: false,
+}
+
+apex {
+ name: "com.google.cf.rild",
+ manifest: "apex_manifest.json",
+ key: "com.google.cf.apex.key",
+ certificate: ":com.google.cf.apex.certificate",
+ file_contexts: "file_contexts",
+ use_vndk_as_stable: true,
+ updatable: false,
+ // Install the apex in /vendor/apex
+ soc_specific: true,
+ binaries: [
+ "libcuttlefish-rild",
+ ],
+ native_shared_libs: [
+ "libcuttlefish-ril-2",
+ ],
+ prebuilts: [
+ "android.hardware.telephony.gsm.prebuilt.xml",
+ "android.hardware.telephony.ims.prebuilt.xml",
+ "com.google.cf.rild.rc",
+ "ld.config.txt",
+ ],
+ vintf_fragments: [":libril-modem-lib-manifests"],
+ overrides: [
+ "libril",
+ "libreference-ril",
+ "rild",
+ ],
+}
diff --git a/apex/com.google.cf.rild/apex_manifest.json b/apex/com.google.cf.rild/apex_manifest.json
new file mode 100644
index 0000000..c096789
--- /dev/null
+++ b/apex/com.google.cf.rild/apex_manifest.json
@@ -0,0 +1,4 @@
+{
+ "name": "com.google.cf.rild",
+ "version": 1
+}
diff --git a/apex/com.google.cf.rild/com.google.cf.rild.rc b/apex/com.google.cf.rild/com.google.cf.rild.rc
new file mode 100644
index 0000000..ff8b888
--- /dev/null
+++ b/apex/com.google.cf.rild/com.google.cf.rild.rc
@@ -0,0 +1,5 @@
+service vendor.ril-daemon /apex/com.google.cf.rild/bin/hw/libcuttlefish-rild
+ class main
+ user radio
+ group radio inet misc audio log readproc wakelock
+ capabilities BLOCK_SUSPEND NET_ADMIN NET_RAW
diff --git a/apex/com.google.cf.rild/file_contexts b/apex/com.google.cf.rild/file_contexts
new file mode 100644
index 0000000..fc0d328
--- /dev/null
+++ b/apex/com.google.cf.rild/file_contexts
@@ -0,0 +1,3 @@
+(/.*)? u:object_r:vendor_file:s0
+/bin/hw/libcuttlefish-rild u:object_r:libcuttlefish_rild_exec:s0
+/etc/permissions(/.*)? u:object_r:vendor_configs_file:s0
diff --git a/apex/com.google.cf.rild/ld.config.txt b/apex/com.google.cf.rild/ld.config.txt
new file mode 100644
index 0000000..c088357
--- /dev/null
+++ b/apex/com.google.cf.rild/ld.config.txt
@@ -0,0 +1,7 @@
+dir.myapex = /apex/com.google.cf.rild/bin
+
+[myapex]
+additional.namespaces = vndk
+namespace.default.search.paths = /apex/com.google.cf.rild/${LIB}
+# For android.hardware.radio-service.compat
+namespace.vndk.permitted.paths = /vendor/${LIB}/hw
diff --git a/apex/com.google.cf.wifi/Android.bp b/apex/com.google.cf.wifi/Android.bp
new file mode 100644
index 0000000..3a57dd6
--- /dev/null
+++ b/apex/com.google.cf.wifi/Android.bp
@@ -0,0 +1,73 @@
+// Copyright (C) 2021 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package {
+ default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+prebuilt_etc {
+ name: "com.google.cf.wifi.rc",
+ src: "com.google.cf.wifi.rc",
+ installable: false,
+}
+
+prebuilt_etc {
+ name: "wpa_supplicant.conf.cf",
+ src: ":wpa_supplicant_template.conf",
+ filename: "wpa_supplicant.conf",
+ relative_install_path: "wifi",
+ installable: false,
+}
+
+apex_defaults {
+ name: "com.google.cf.wifi.defaults",
+ // Name expected by wpa_supplicant when it looks for config files.
+ apex_name: "com.android.wifi.hal",
+ manifest: "apex_manifest.json",
+ key: "com.google.cf.apex.key",
+ certificate: ":com.google.cf.apex.certificate",
+ file_contexts: "file_contexts",
+ use_vndk_as_stable: true,
+ updatable: false,
+ // Install the apex in /vendor/apex
+ soc_specific: true,
+ binaries: [
+ "rename_netiface",
+ "setup_wifi",
+ "wpa_supplicant_cf",
+ ],
+ prebuilts: [
+ "android.hardware.wifi.prebuilt.xml",
+ "com.google.cf.wifi.rc",
+ "wpa_supplicant.conf.cf",
+ "wpa_supplicant_overlay.conf.cf",
+ ],
+ // TODO(b/202992812): Use the vintf_fragment from the wpa_supplicant project.
+ vintf_fragments: ["com.google.cf.wifi.xml"],
+}
+
+apex {
+ name: "com.google.cf.wifi",
+ defaults: ["com.google.cf.wifi.defaults"],
+ prebuilts: [
+ "android.hardware.wifi.passpoint.prebuilt.xml",
+ ],
+ multi_install_skip_symbol_files: true,
+}
+
+apex {
+ name: "com.google.cf.wifi.no-passpoint",
+ defaults: ["com.google.cf.wifi.defaults"],
+ multi_install_skip_symbol_files: true,
+}
diff --git a/apex/com.google.cf.wifi/apex_manifest.json b/apex/com.google.cf.wifi/apex_manifest.json
new file mode 100644
index 0000000..ffd1a2c
--- /dev/null
+++ b/apex/com.google.cf.wifi/apex_manifest.json
@@ -0,0 +1,4 @@
+{
+ "name": "com.android.wifi.hal",
+ "version": 1
+}
diff --git a/apex/com.google.cf.wifi/com.google.cf.wifi.rc b/apex/com.google.cf.wifi/com.google.cf.wifi.rc
new file mode 100644
index 0000000..8551506
--- /dev/null
+++ b/apex/com.google.cf.wifi/com.google.cf.wifi.rc
@@ -0,0 +1,12 @@
+service rename_eth0 /apex/com.android.wifi.hal/bin/rename_netiface eth0 buried_eth0
+ oneshot
+
+service setup_wifi /apex/com.android.wifi.hal/bin/setup_wifi
+ oneshot
+
+service wpa_supplicant /apex/com.android.wifi.hal/bin/hw/wpa_supplicant_cf -g@android:wpa_wlan0
+ interface aidl android.hardware.wifi.supplicant.ISupplicant/default
+ socket wpa_wlan0 dgram 660 wifi wifi
+ group system wifi inet
+ disabled
+ oneshot
diff --git a/guest/hals/ril/reference-libril/[email protected] b/apex/com.google.cf.wifi/com.google.cf.wifi.xml
similarity index 62%
copy from guest/hals/ril/reference-libril/[email protected]
copy to apex/com.google.cf.wifi/com.google.cf.wifi.xml
index 97bf66d..772096c 100644
--- a/guest/hals/ril/reference-libril/[email protected]
+++ b/apex/com.google.cf.wifi/com.google.cf.wifi.xml
@@ -1,10 +1,10 @@
<manifest version="1.0" type="device">
<hal format="hidl">
- <name>android.hardware.radio.config</name>
+ <name>android.hardware.wifi.supplicant</name>
<transport>hwbinder</transport>
- <version>1.3</version>
+ <version>1.4</version>
<interface>
- <name>IRadioConfig</name>
+ <name>ISupplicant</name>
<instance>default</instance>
</interface>
</hal>
diff --git a/apex/com.google.cf.wifi/file_contexts b/apex/com.google.cf.wifi/file_contexts
new file mode 100644
index 0000000..b11e272
--- /dev/null
+++ b/apex/com.google.cf.wifi/file_contexts
@@ -0,0 +1,5 @@
+(/.*)? u:object_r:vendor_file:s0
+/bin/rename_netiface u:object_r:rename_netiface_exec:s0
+/bin/setup_wifi u:object_r:setup_wifi_exec:s0
+/bin/hw/wpa_supplicant_cf u:object_r:hal_wifi_supplicant_default_exec:s0
+/etc/permissions(/.*)? u:object_r:vendor_configs_file:s0
diff --git a/apex/com.google.cf.wifi_hwsim/Android.bp b/apex/com.google.cf.wifi_hwsim/Android.bp
new file mode 100644
index 0000000..6b8b9ce
--- /dev/null
+++ b/apex/com.google.cf.wifi_hwsim/Android.bp
@@ -0,0 +1,81 @@
+// Copyright (C) 2021 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+soong_namespace {
+ imports: [
+ "device/generic/goldfish",
+ ],
+}
+
+package {
+ default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+prebuilt_etc {
+ name: "com.google.cf.wifi_hwsim.rc",
+ src: "com.google.cf.wifi_hwsim.rc",
+ installable: false,
+}
+
+cc_binary {
+ name: "[email protected]_cf",
+ defaults: ["[email protected]_default"],
+ shared_libs: ["libwifi-hal_cf"],
+ static_libs: ["[email protected]_cf"],
+}
+
+cc_library_static {
+ name: "[email protected]_cf",
+ defaults: ["[email protected]_defaults"],
+ shared_libs: ["libwifi-hal_cf"],
+}
+
+cc_library_shared {
+ name: "libwifi-hal_cf",
+ defaults: ["libwifi-hal_defaults"],
+ whole_static_libs: ["libwifi-hal-emu"],
+}
+
+apex {
+ name: "com.google.cf.wifi_hwsim",
+ // Name expected by wpa_supplicant when it looks for config files.
+ apex_name: "com.android.wifi.hal",
+ manifest: "apex_manifest.json",
+ key: "com.google.cf.apex.key",
+ certificate: ":com.google.cf.apex.certificate",
+ file_contexts: "file_contexts",
+ use_vndk_as_stable: true,
+ updatable: false,
+ // Install the apex in /vendor/apex
+ soc_specific: true,
+ binaries: [
+ "mac80211_create_radios",
+ "rename_netiface",
+ "wpa_supplicant_cf",
+ "hostapd_cf",
+ "[email protected]_cf",
+ ],
+ sh_binaries: ["init.wifi.sh_apex"],
+ prebuilts: [
+ "android.hardware.wifi.direct.prebuilt.xml",
+ "android.hardware.wifi.passpoint.prebuilt.xml",
+ "android.hardware.wifi.prebuilt.xml",
+ "com.google.cf.wifi_hwsim.rc",
+ "p2p_supplicant.conf.cf",
+ "wpa_supplicant.conf.cf",
+ "wpa_supplicant_overlay.conf.cf",
+ ],
+ // TODO(b/202992812): Use the vintf_fragment from the wpa_supplicant project.
+ vintf_fragments: ["com.google.cf.wifi_hwsim.xml"],
+}
diff --git a/apex/com.google.cf.wifi_hwsim/apex_manifest.json b/apex/com.google.cf.wifi_hwsim/apex_manifest.json
new file mode 100644
index 0000000..ffd1a2c
--- /dev/null
+++ b/apex/com.google.cf.wifi_hwsim/apex_manifest.json
@@ -0,0 +1,4 @@
+{
+ "name": "com.android.wifi.hal",
+ "version": 1
+}
diff --git a/apex/com.google.cf.wifi_hwsim/com.google.cf.wifi_hwsim.rc b/apex/com.google.cf.wifi_hwsim/com.google.cf.wifi_hwsim.rc
new file mode 100644
index 0000000..3dd0440
--- /dev/null
+++ b/apex/com.google.cf.wifi_hwsim/com.google.cf.wifi_hwsim.rc
@@ -0,0 +1,41 @@
+
+service rename_eth0 /apex/com.android.wifi.hal/bin/rename_netiface eth0 buried_eth0
+ oneshot
+
+service init_wifi_sh /apex/com.android.wifi.hal/bin/init.wifi.sh
+ class late_start
+ user root
+ group root wakelock wifi
+ oneshot
+ disabled # Started on post-fs-data
+
+service wpa_supplicant /apex/com.android.wifi.hal/bin/hw/wpa_supplicant_cf \
+ -O/data/vendor/wifi/wpa/sockets -puse_p2p_group_interface=1p2p_device=1 \
+ -m/apex/com.android.wifi.hal/etc/wifi/p2p_supplicant.conf \
+ -g@android:wpa_wlan0 -dd
+ interface aidl android.hardware.wifi.supplicant.ISupplicant/default
+ socket wpa_wlan0 dgram 660 wifi wifi
+ group system wifi inet
+ disabled
+ oneshot
+
+service hostapd /apex/com.android.wifi.hal/bin/hw/hostapd_cf
+ interface aidl android.hardware.wifi.hostapd.IHostapd/default
+ class main
+ capabilities NET_ADMIN NET_RAW
+ user wifi
+ group wifi net_raw net_admin
+ disabled
+ oneshot
+
+service vendor.wifi_hal_legacy /apex/com.android.wifi.hal/bin/hw/[email protected]_cf
+ interface [email protected]::IWifi default
+ interface [email protected]::IWifi default
+ interface [email protected]::IWifi default
+ interface [email protected]::IWifi default
+ interface [email protected]::IWifi default
+ interface [email protected]::IWifi default
+ class hal
+ capabilities NET_ADMIN NET_RAW SYS_MODULE
+ user wifi
+ group wifi gps
diff --git a/apex/com.google.cf.wifi_hwsim/com.google.cf.wifi_hwsim.xml b/apex/com.google.cf.wifi_hwsim/com.google.cf.wifi_hwsim.xml
new file mode 100644
index 0000000..05eb2c0
--- /dev/null
+++ b/apex/com.google.cf.wifi_hwsim/com.google.cf.wifi_hwsim.xml
@@ -0,0 +1,19 @@
+<manifest version="1.0" type="device">
+ <hal format="aidl">
+ <name>android.hardware.wifi.supplicant</name>
+ <fqname>ISupplicant/default</fqname>
+ </hal>
+ <hal format="aidl">
+ <name>android.hardware.wifi.hostapd</name>
+ <fqname>IHostapd/default</fqname>
+ </hal>
+ <hal format="hidl">
+ <name>android.hardware.wifi</name>
+ <transport>hwbinder</transport>
+ <version>1.6</version>
+ <interface>
+ <name>IWifi</name>
+ <instance>default</instance>
+ </interface>
+ </hal>
+</manifest>
diff --git a/apex/com.google.cf.wifi_hwsim/file_contexts b/apex/com.google.cf.wifi_hwsim/file_contexts
new file mode 100644
index 0000000..083cfed
--- /dev/null
+++ b/apex/com.google.cf.wifi_hwsim/file_contexts
@@ -0,0 +1,8 @@
+(/.*)? u:object_r:vendor_file:s0
+/bin/rename_netiface u:object_r:rename_netiface_exec:s0
+/bin/init\.wifi\.sh u:object_r:init_wifi_sh_exec:s0
+/bin/hw/wpa_supplicant_cf u:object_r:hal_wifi_supplicant_default_exec:s0
+/bin/hw/hostapd_cf u:object_r:hal_wifi_hostapd_default_exec:s0
+/bin/mac80211_create_radios u:object_r:mac80211_create_radios_exec:s0
+/etc/permissions(/.*)? u:object_r:vendor_configs_file:s0
+/bin/hw/android\.hardware\.wifi@1\.0-service_cf u:object_r:hal_wifi_default_exec:s0
diff --git a/host/commands/tapsetiff/Android.bp b/apex/keys/Android.bp
similarity index 67%
copy from host/commands/tapsetiff/Android.bp
copy to apex/keys/Android.bp
index 1d7dedb..85184bc 100644
--- a/host/commands/tapsetiff/Android.bp
+++ b/apex/keys/Android.bp
@@ -1,5 +1,4 @@
-//
-// Copyright (C) 2020 The Android Open Source Project
+// Copyright (C) 2021 The Android Open Source Project
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
@@ -17,7 +16,13 @@
default_applicable_licenses: ["Android-Apache-2.0"],
}
-sh_binary_host {
- name: "tapsetiff",
- src: "tapsetiff.py",
+apex_key {
+ name: "com.google.cf.apex.key",
+ public_key: "com.google.cf.apex.avbpubkey",
+ private_key: "com.google.cf.apex.pem",
+}
+
+android_app_certificate {
+ name: "com.google.cf.apex.certificate",
+ certificate: "com.google.cf.apex",
}
diff --git a/apex/keys/com.google.cf.apex.avbpubkey b/apex/keys/com.google.cf.apex.avbpubkey
new file mode 100644
index 0000000..1fc39e1
--- /dev/null
+++ b/apex/keys/com.google.cf.apex.avbpubkey
Binary files differ
diff --git a/apex/keys/com.google.cf.apex.pem b/apex/keys/com.google.cf.apex.pem
new file mode 100644
index 0000000..59de332
--- /dev/null
+++ b/apex/keys/com.google.cf.apex.pem
@@ -0,0 +1,51 @@
+-----BEGIN RSA PRIVATE KEY-----
+MIIJKQIBAAKCAgEAvAG5AIYljraeMBaaVr+UYPFZ/GEDn1+irto2iWtPJxaSIQwU
+t0GMU5DtRMds3XK0PaQOYusoQXQnf3nnb6Ake8NtifsI+pw2HRnx+bwlbvoUBlA4
+g0hpMQThdryevg1hT8bdgQDRm8LI/xIGZ/HH5jKgNHcEmxJuVTmBfSjokaIJDb/M
+ZrTr9agFALIHh6JAdRDXUE4Myr3a8pzeAN71Y5Ll99c1Ni3uc5Fx/83q+vtlL5qE
+zDCsBj1q7gvkdiYFtEGsm+tJ5+sG5/aUKaOq05u0vZRsLInlacub/6Vy5G+uBAXc
+Lzx14rXvzcKkcvgbkpmedPdbv3JR4Pg0oGtgPXG9Qh3bU1XsjaYUzp9c1jhQjs+q
+qBGKOKex1sVF/6/Wn6EjYGKawxHPQtFEp5DcIv7nrQDPFWy6GA4CmO/vaWfQcmIA
+9ikCZGuqDhKJpmJ4WO145u1qgnNHsBLwHMtxHoz7nq0bJUcdCxbrNutUkn4lFuFs
+JUUZdFxKeJkYcT+0+Ml1P6girGXaiLE8JyYBTps+Y91mGnMuNDezseZBEYpX1rP+
+Qx5pdbMFt8anp5bOar76dyGI/wq12kgfnpyweItXRULiIS9EsPxT6birh1BzXtpq
+jnsdEpC5uXidise1rvdNkrgbpCZC9ZDpRzBORWz1OW8+EhmoGhtvBPD6wLsCAwEA
+AQKCAgAW5SrPaoa2W3zmJEqFV+1M5Pdtaa8UQIRCQOa1U3EfNHt1NNBtBLl/D74l
+Sxfx298hRpJN749GcUvCFWleyaTHwaPcUsrkIhPg9WDnZcc1PZUks64+JppQ0uRW
+HmBCisSX/4LIC/56tnzduyc2j1YlrXKfEQNpkxQGousm/81att3dY8cTluLJVr3N
+OOD73oF1ACkIaYjbQ8WfGAVdG8nMZ35D8VxUjcFlJ4g3e68rA2RuKKYVa7P3SpF0
+DdSzoqu9KOZJUpz8dj2wD/I5I+pQvLyE/ccyoVRjztzfhBl6wjLx4HjQ887zXe6n
+IxX9vkM1Vina2qi8psJb4D4gbxMYEvXnOhequ1TSvd03f91gR5D9KFxspGF8NHRA
+gkzKT7QGqgOS31IANC0wIdH5tVrLSVxhtq5BKxdxywTZz4wA/Gy32o7D7phX9PhH
+MZDSplwi92ft1UoU1VLM5eCRSDJf9LM3HVXTpzlmMwCalR+AVnCxNa6rKvDfAbmn
+BVOoh8vNkEfRBwmI6ctXaC7VVA+E4JdWMKk7BbKqU4yUVHrdSCyjwxomAh3G539w
+0YnS7mR8kw/D9ppkQ5bm6ndtu8ABd+VfBPzlvc3kgLjlco7zENNqMOhgrm5lKZsS
+s5WCrZ4feeeC4FZAUXf17SYas6WfB9Hr64Fs81vZmwfNpmgMGQKCAQEA6CsiJCh4
+FAbuLZAAJtJ3z/4uoJTLqfHPOHZFww2PJ7f5CwYIdOxBQ4/PBjz8wreajur3eYai
+T9uhKbG2+elO48ReMREytcpGox9t36wbxrrE185XpI4lQITvEyOv9fsV0af4Vk7W
+KljV5IMMauYwwouUwYV4VPQHVOUMKNy0u/68A6NTjIAXU41pQHzo+bCtyCjpw8ol
+7dQxHzOFbngoSv0s+FT1a+myDJPSAj0BjDTlsaD+Gplcvag4qMOA28IBjcFiv4l5
+e75RF0CIWzRvOJsR3G7WkhxTH1jn4HYOjoYRAZ5YTHq7QM5/xZdulA0sT2zWzY6V
+YlOICG9jZTpI/wKCAQEAz04eDQtDme2jvzdMft0OebsEXuRudCpGiZ3hkPthBelk
+eeFEt9yl2oU9fOOQhjY369gLK3tkN+O6hpGV9bWC2Iua1o7gsSZDp8CKZFOK9JD7
+aqvmND0R3tRCqVaaraIqgWx7kFg5x7Lfgi/KFuXZy7umCbVQqtxDBaSW+Nso2m9m
+0mnr41r9Z52gqJV+z1zyjRY4wswtAmUDcRDnwkJsX6hKFxTRvxRvy1JK1whBgflM
+3oHjUNQFSeeKa2rdZzDSy7ZRCTwl5gZI2CsURX3XmFvYOXdioDACUlpcjzK8Trcq
+/2+gz/2nLmMgVvp5tHYhYHovEks8BoAyAofFyQbsRQKCAQBSH5/OBnqjKuhpOXy0
+PtKewhygNMHt9VkFceCvZEZ1GECBw7qOEVvsmBv06vHFtsh3MWoklJkpglj5tKEy
+uXJsYvOmi5zSbSCbZuyop+qTW1FxvM2HqbhHoD4pGQCPFCfdp3rSnMRo6k+Oq0Rj
+M9Wfm1wdMCcmdcN6JiMs+RT9QtgiuU0+b7jQlz7ZztViLTrriH1YAlN0UxClJsZW
+Ey69h9y2YucFKv8OL+OjYwz/GV7+fCImKoWBmNWh7LXSBkgianuRoQFV4jYw8WTK
+TjvhXAjvXk2MFXTZq8spvNjdVVMCrY4yT1+ZRvIvZKd6u0YnOiqpP3xb8Yw235/b
+GMjlAoIBAQCav7ubDR+Hlme34/XMdgPKRxr6Ixd4y94f+KVbbut8WD9S5CBCCAoe
+13uQ8Ob/6RVRjtK3wMKNHggtUBxbcQWd1IjfRYTheKjkXsxwHBUMf/XOKUgNEtF2
+P4kLk8SffQCx4GNU2yc2tYY3TqlS8n3koc1OTfVLtmSpn7W7Sw5yENr2k28tJs0n
+PfmiHwaskLvXKhFxCK1IrlMlYfM/hgoUVjIIjNgOBZl2c5W+c0FDXvBM4TTpL3xL
+MPaZPQrNbxrMSuqvNCEuVt6lz3KwdUItT9JXA5Gx9mSlSSLzGnKLaBxG1fN7j+Pu
+srx/cTbMyaoctNjSlSrXx3aNgQDaEbrpAoIBAQCxwuK5O9dVHIL8KDh20GrUSVrI
+OapyM7xEjiOJbqEhRbfww3ZHJmw6KYahOnJ5M4gT+23olqRK0pkV80O1H0hfBOF6
+WUlYxW/fBB2egSM568mZ0lXZAMDRxF19XpxzbPUlzTVoA9sZm9Djq6uqWCjYL0Rk
+0aj3DlMMPNMf3EBZJr2pI/GT1WssqKpvTv5gYXvsdoqvpGucW6z/MvilKgbU+RvQ
+V6zDyNdAepw65MCrH+doNroe9NEKS6Fg7RTgMKBWaHTaHJuayj+5/IvkHJ7ULTpW
+NoKv6EkvL2FuVsVa4fY+3KMhJamr4IkhCHhKeNDrAxEyHZ1QjAjhH9W0QDP0
+-----END RSA PRIVATE KEY-----
diff --git a/apex/keys/com.google.cf.apex.pk8 b/apex/keys/com.google.cf.apex.pk8
new file mode 100644
index 0000000..424387a
--- /dev/null
+++ b/apex/keys/com.google.cf.apex.pk8
Binary files differ
diff --git a/apex/keys/com.google.cf.apex.x509.pem b/apex/keys/com.google.cf.apex.x509.pem
new file mode 100644
index 0000000..db073ba
--- /dev/null
+++ b/apex/keys/com.google.cf.apex.x509.pem
@@ -0,0 +1,33 @@
+-----BEGIN CERTIFICATE-----
+MIIFyTCCA7ECFEDbudaUmJ6QxCAviuzTSQwh55rKMA0GCSqGSIb3DQEBCwUAMIGf
+MQswCQYDVQQGEwJVUzETMBEGA1UECAwKQ2FsaWZvcm5pYTEWMBQGA1UEBwwNTW91
+bnRhaW4gVmlldzEQMA4GA1UECgwHQW5kcm9pZDEQMA4GA1UECwwHQW5kcm9pZDEi
+MCAGCSqGSIb3DQEJARYTYW5kcm9pZEBhbmRyb2lkLmNvbTEbMBkGA1UEAwwSY29t
+Lmdvb2dsZS5jZi5yaWxkMCAXDTIxMTAwMTE3MjQyMVoYDzQ3NTkwODI4MTcyNDIx
+WjCBnzELMAkGA1UEBhMCVVMxEzARBgNVBAgMCkNhbGlmb3JuaWExFjAUBgNVBAcM
+DU1vdW50YWluIFZpZXcxEDAOBgNVBAoMB0FuZHJvaWQxEDAOBgNVBAsMB0FuZHJv
+aWQxIjAgBgkqhkiG9w0BCQEWE2FuZHJvaWRAYW5kcm9pZC5jb20xGzAZBgNVBAMM
+EmNvbS5nb29nbGUuY2YucmlsZDCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoC
+ggIBAKGd7f1fKnpky183oJHp5xAam+LeyMcCMg+VPaRMg5T2Uw4kPlJrwxDx0n6S
+WNtqVvcZMRfUREw6BW6mWhOae1rarVSJDxo+pzcju66zVYMf38FqzrBLPSzkKOvK
+pC3WiTs193+lrWsL6a3XtNx3gN3J4cc4f+gEASiE93mbN/pHirIgpLwmZMpaHAXd
+KZBk8iXPrwusdCAi0F6v9AxP0JUhYBmqr+/q5mFHevRq7UBykBjRqNTylmDPjjxu
+fRDkOmnzs8/htToT03NXrDhxod5GIZ/+NsVaTECZ4PK/+BBnMBg/FRE4kPpolMkp
+Nt72pkGDL0whz7RrUxhHfFDVKngmmspBpBIF4bVXQz5yuYqDaEiZv/7x8yjsbkkF
++eraEi9HBy6klhlbYV2XDA/qvV8twocJS/5Qql9bGmH/fHX3SbXTI/RD4kiopxXL
+rCLiV5ABjaFEUp5ub2IHP4W/lXpWqc1GFZfg59wL6NO/dJGO7UsHW/M2euJ4RU4i
+w3kl0J2TmjM1mCbWPsYkyCtW+QwkSz1RpB2cI2v+oYc5vmIceZuu3OY80mzvhqEK
+45Nu1qGoExmrdgVqF6h8LHMSRAMAlx+13Glt2QNEdaEsc9E1oPxXXK0oOLEI/F0B
+9pGGMgVZFPSGtQ8nWNAxy2v+WwtzNQO9p5ra6atYHkbwfhcdAgMBAAEwDQYJKoZI
+hvcNAQELBQADggIBAFpj7pYQHw75MK1Np27IVwhegZrJNb2g8lpYuD/Qzx9bPBTg
+BJ6hSnjPQwgFeAA1mtyh9CzJPzBRrGXJz/zp/VojFs1G4vqm3if+MC+ONKzxtlf1
+q4IPd/DsFce9Ak2NDyAp6IFDZhtEQxv48CclYy2JsIVqlPh2aWN2/VbHQ2e/D8ZK
+MIrTiSPjogDSPcK4MLbavSCQlxZIr+bfNhqm78oWhrpTrJu0+6rXNDJ82LpobmEV
+lp2UmFPVc1sm+Al317Wo5durfF99YSDOF2u9lKO81X4b5Jjc0a6n1q/hHlrMSMuq
++5wn+KVtKRAZf5dU7YGRTPLYKq1Yf0Zygd5lXxG2xhtwaHnwTY3g5cgKLHGzJ9hk
+FbLxVR86ZXiFnZyEncfC8PjUBX6fK2hy1bi1GZSj6jRwsUTx6ay5m28Y4EA+ix89
+hcqrXNR2dumpZTtjirjIrr2hbQB/Mf+LGSmUSZzLaL8vF6Owaxrqg7r3L4NnY6o7
+P45QehDTmm6DQg/8y47KPeKuHIf3JjW2WavcyUF0d/FtjXtPGB+t9Uskfhmv+qIm
+/3RAGckgLIi1+38bwVeHI+EnljZ2PLaXarEFjecI9HmA7A40MQWxaQns91nIjKDE
+8jpIVI+lTRpCPNJAfEm+txvE6PnlAXqDCIhIfl/sX/sIJguqoI/NV+1xJYQg
+-----END CERTIFICATE-----
diff --git a/build/Android.bp b/build/Android.bp
index d516313..dbae9bf 100644
--- a/build/Android.bp
+++ b/build/Android.bp
@@ -23,6 +23,7 @@
module_type: "cvd_host_package",
config_namespace: "cvd",
value_variables: [
+ "grub_config",
"launch_configs",
"custom_action_config",
"custom_action_servers",
@@ -33,21 +34,28 @@
"android.hardware.automotive.vehicle@2.0-virtualization-grpc-server",
"adb",
"adb_connector",
- "adbshell",
"allocd",
"allocd_client",
"assemble_cvd",
+ "avbtool",
"bt_connector",
"common_crosvm",
"config_server",
"console_forwarder",
"crosvm",
+ "cvd",
+ "cvd_internal_host_bugreport",
+ "cvd_internal_start",
+ "cvd_internal_status",
+ "cvd_internal_stop",
"cvd_host_bugreport",
"cvd_status",
+ "cvd_test_gce_driver",
"extract-ikconfig",
"extract-vmlinux",
"fsck.f2fs",
"gnss_grpc_proxy",
+ "health",
"kernel_log_monitor",
"launch_cvd",
"libgrpc++",
@@ -61,27 +69,35 @@
"metrics",
"mkbootfs",
"mkbootimg",
- "mkenvimage",
+ "mkenvimage_slim",
"modem_simulator",
"ms-tpm-20-ref",
+ "mcopy",
+ "mmd",
+ "mtools",
"newfs_msdos",
"powerwash_cvd",
"restart_cvd",
"root-canal",
- // TODO(b/186487510): remove libchrome and libbacktrace when ASan-related dependency issue is resolved.
- "libchrome",
- "libbacktrace",
"run_cvd",
"secure_env",
+ "cvd_send_sms",
"socket_vsock_proxy",
"stop_cvd",
- "tapsetiff",
"tombstone_receiver",
"toybox",
"unpack_bootimg",
- "vnc_server",
"webRTC",
"webrtc_operator",
+ "operator_proxy",
+ "wmediumd",
+ "wmediumd_control",
+ "wmediumd_gen_config",
+]
+
+cvd_openwrt_images = [
+ "kernel_for_openwrt",
+ "openwrt_rootfs",
]
cvd_bluetooth_config_files = [
@@ -97,14 +113,18 @@
cvd_host_webrtc_assets = [
"webrtc_adb.js",
"webrtc_app.js",
+ "webrtc_index.js",
"webrtc_controls.js",
"webrtc_cf.js",
+ "webrtc_server_connector.js",
"webrtc_index.html",
+ "webrtc_client.html",
"webrtc_rootcanal.js",
"webrtc_server.crt",
"webrtc_server.key",
"webrtc_server.p12",
"webrtc_style.css",
+ "webrtc_index.css",
"webrtc_controls.css",
"webrtc_trusted.pem",
]
@@ -118,11 +138,15 @@
cvd_host_seccomp_policy_x86_64 = [
"9p_device.policy_x86_64",
"balloon_device.policy_x86_64",
+ "battery.policy_x86_64",
"block_device.policy_x86_64",
"cras_audio_device.policy_x86_64",
+ "cras_snd_device.policy_x86_64",
"fs_device.policy_x86_64",
"gpu_device.policy_x86_64",
+ "gpu_render_server.policy_x86_64",
"input_device.policy_x86_64",
+ "iommu_device.policy_x86_64",
"net_device.policy_x86_64",
"null_audio_device.policy_x86_64",
"pmem_device.policy_x86_64",
@@ -138,13 +162,16 @@
"xhci.policy_x86_64",
]
-cvd_host_seccomp_policy_arm64 = [
+cvd_host_seccomp_policy_aarch64 = [
"9p_device.policy_aarch64",
"balloon_device.policy_aarch64",
+ "battery.policy_aarch64",
"block_device.policy_aarch64",
"cras_audio_device.policy_aarch64",
+ "cras_snd_device.policy_aarch64",
"fs_device.policy_aarch64",
"gpu_device.policy_aarch64",
+ "gpu_render_server.policy_aarch64",
"input_device.policy_aarch64",
"net_device.policy_aarch64",
"null_audio_device.policy_aarch64",
@@ -159,6 +186,23 @@
"xhci.policy_aarch64",
]
+cvd_host_qemu_bootloader = [
+ "bootloader_qemu_x86_64",
+ "bootloader_qemu_aarch64",
+ "bootloader_qemu_arm",
+]
+
+prebuilt_etc_host {
+ name: "cvd_avb_testkey",
+ filename: "cvd_avb_testkey.pem",
+ src: ":avb_testkey_rsa4096",
+}
+
+cvd_host_avb_testkey = [
+ "cvd_avb_pubkey",
+ "cvd_avb_testkey",
+]
+
cvd_host_package_customization {
name: "cvd-host_package",
deps: cvd_host_tools +
@@ -166,8 +210,11 @@
multilib: {
common: {
deps: cvd_host_webrtc_assets +
+ cvd_host_avb_testkey +
cvd_host_model_simulator_files +
- cvd_bluetooth_config_files,
+ cvd_host_qemu_bootloader +
+ cvd_bluetooth_config_files +
+ cvd_openwrt_images,
},
},
@@ -182,7 +229,7 @@
arm64: {
multilib: {
common: {
- deps: cvd_host_seccomp_policy_arm64,
+ deps: cvd_host_seccomp_policy_aarch64,
},
},
},
diff --git a/build/README.md b/build/README.md
index dc66c0a..cfba9c5 100644
--- a/build/README.md
+++ b/build/README.md
@@ -1,25 +1,26 @@
## Custom Actions
-To add custom actions to the WebRTC control panel, create a custom action config
-JSON file in your virtual device product makefile directory, create a
-`prebuilt_etc_host` module for the JSON file with `sub_dir`
-`cvd_custom_action_config`, then set the build variable
-`SOONG_CONFIG_cvd_custom_action_config` to the name of that module. For example:
+To add custom actions to the WebRTC control panel:
-```
-Android.bp:
- prebuilt_etc_host {
- name: "my_custom_action_config.json",
- src: "my_custom_action_config.json",
- // The sub_dir must always equal the following value:
- sub_dir: "cvd_custom_action_config",
- }
+* Create a custom action config JSON file in your virtual device product
+ makefile directory.
+* Create a `prebuilt_etc_host` module for the JSON file with `sub_dir`
+ `cvd_custom_action_config`
+* Set the Soong config variable `custom_action_config` in the `cvd` namespace
+ to the name of that module. For example:
-my_virtual_device.mk:
- SOONG_CONFIG_NAMESPACES += cvd
- SOONG_CONFIG_cvd += custom_action_config
- SOONG_CONFIG_cvd_custom_action_config := my_custom_action_config.json
-```
+ ```
+ Android.bp:
+ prebuilt_etc_host {
+ name: "my_custom_action_config.json",
+ src: "my_custom_action_config.json",
+ // The sub_dir must always equal the following value:
+ sub_dir: "cvd_custom_action_config",
+ }
+
+ my_virtual_device.mk:
+ $(call soong_config_set, cvd, custom_action_config, my_custom_action_config.json)
+ ```
TODO(b/171709037): Add documentation to source.android.com
diff --git a/build/cvd-host-package.go b/build/cvd-host-package.go
index b025386..5ff885e 100644
--- a/build/cvd-host-package.go
+++ b/build/cvd-host-package.go
@@ -56,6 +56,12 @@
{Mutator: "arch", Variation: android.Common.String()},
}
for _, dep := range strings.Split(
+ ctx.Config().VendorConfig("cvd").String("grub_config"), " ") {
+ if ctx.OtherModuleExists(dep) {
+ ctx.AddVariationDependencies(variations, cvdHostPackageDependencyTag, dep)
+ }
+ }
+ for _, dep := range strings.Split(
ctx.Config().VendorConfig("cvd").String("launch_configs"), " ") {
if ctx.OtherModuleExists(dep) {
ctx.AddVariationDependencies(variations, cvdHostPackageDependencyTag, dep)
@@ -81,7 +87,7 @@
func (c *cvdHostPackage) GenerateAndroidBuildActions(ctx android.ModuleContext) {
zipFile := android.PathForModuleOut(ctx, "package.zip")
- c.CopyDepsToZip(ctx, zipFile)
+ c.CopyDepsToZip(ctx, c.GatherPackagingSpecs(ctx), zipFile)
// Dir where to extract the zip file and construct the final tar.gz from
packageDir := android.PathForModuleOut(ctx, ".temp")
diff --git a/common/frontend/socket_vsock_proxy/Android.bp b/common/frontend/socket_vsock_proxy/Android.bp
index 9335408..427d9c2 100644
--- a/common/frontend/socket_vsock_proxy/Android.bp
+++ b/common/frontend/socket_vsock_proxy/Android.bp
@@ -23,6 +23,7 @@
"main.cpp",
],
shared_libs: [
+ "libext2_blkid",
"libbase",
"libcuttlefish_fs",
"libcuttlefish_utils",
@@ -32,6 +33,7 @@
],
static_libs: [
"libgflags",
+ "libcuttlefish_utils",
],
target: {
host: {
diff --git a/common/frontend/socket_vsock_proxy/main.cpp b/common/frontend/socket_vsock_proxy/main.cpp
index cf68821..5d08790 100644
--- a/common/frontend/socket_vsock_proxy/main.cpp
+++ b/common/frontend/socket_vsock_proxy/main.cpp
@@ -15,19 +15,17 @@
*/
#include <set>
-#include <thread>
#include <android-base/logging.h>
#include <gflags/gflags.h>
#include "common/libs/fs/shared_fd.h"
+#include "common/libs/utils/socket2socket_proxy.h"
#include "host/commands/kernel_log_monitor/utils.h"
#ifdef CUTTLEFISH_HOST
#include "host/libs/config/logging.h"
#endif // CUTTLEFISH_HOST
-constexpr std::size_t kMaxPacketSize = 8192;
-
DEFINE_string(server, "",
"The type of server to host, `vsock` or `tcp`. When hosting a server "
"of one type, the proxy will take inbound connections of this type and "
@@ -46,109 +44,6 @@
"server and the corresponding port flag will be ignored");
namespace {
-// Sends packets, Shutdown(SHUT_WR) on destruction
-class SocketSender {
- public:
- explicit SocketSender(cuttlefish::SharedFD socket) : socket_{socket} {}
-
- SocketSender(SocketSender&&) = default;
- SocketSender& operator=(SocketSender&&) = default;
-
- SocketSender(const SocketSender&&) = delete;
- SocketSender& operator=(const SocketSender&) = delete;
-
- ~SocketSender() {
- if (socket_.operator->()) { // check that socket_ was not moved-from
- socket_->Shutdown(SHUT_WR);
- }
- }
-
- ssize_t SendAll(const char* packet, ssize_t length) {
- ssize_t written{};
- while (written < length) {
- if (!socket_->IsOpen()) {
- return -1;
- }
- auto just_written =
- socket_->Send(packet + written,
- length - written, MSG_NOSIGNAL);
- if (just_written <= 0) {
- LOG(WARNING) << "Couldn't write to client: "
- << strerror(socket_->GetErrno());
- return just_written;
- }
- written += just_written;
- }
- return written;
- }
-
- private:
- cuttlefish::SharedFD socket_;
-};
-
-class SocketReceiver {
- public:
- explicit SocketReceiver(cuttlefish::SharedFD socket) : socket_{socket} {}
-
- SocketReceiver(SocketReceiver&&) = default;
- SocketReceiver& operator=(SocketReceiver&&) = default;
-
- SocketReceiver(const SocketReceiver&&) = delete;
- SocketReceiver& operator=(const SocketReceiver&) = delete;
-
- // return value will be 0 if Read returns 0 or error
- ssize_t Recv(char* packet, ssize_t length) {
- auto size = socket_->Read(packet, length);
- if (size < 0) {
- size = 0;
- }
-
- return size;
- }
-
- private:
- cuttlefish::SharedFD socket_;
-};
-
-void SocketToVsock(SocketReceiver socket_receiver,
- SocketSender vsock_sender) {
- char packet[kMaxPacketSize] = {};
-
- while (true) {
- ssize_t length = socket_receiver.Recv(packet, kMaxPacketSize);
- if (length == 0 || vsock_sender.SendAll(packet, length) < 0) {
- break;
- }
- }
- LOG(DEBUG) << "Socket to vsock exiting";
-}
-
-void VsockToSocket(SocketSender socket_sender,
- SocketReceiver vsock_receiver) {
- char packet[kMaxPacketSize] = {};
-
- while (true) {
- ssize_t length = vsock_receiver.Recv(packet, kMaxPacketSize);
- if (length == 0) {
- break;
- }
- if (socket_sender.SendAll(packet, length) < 0) {
- break;
- }
- }
- LOG(DEBUG) << "Vsock to socket exiting";
-}
-
-// One thread for reading from shm and writing into a socket.
-// One thread for reading from a socket and writing into shm.
-void HandleConnection(cuttlefish::SharedFD vsock,
- cuttlefish::SharedFD socket) {
- auto socket_to_vsock =
- std::thread(SocketToVsock, SocketReceiver{socket}, SocketSender{vsock});
- VsockToSocket(SocketSender{socket}, SocketReceiver{vsock});
- socket_to_vsock.join();
-}
-
void WaitForAdbdToBeStarted(int events_fd) {
auto evt_shared_fd = cuttlefish::SharedFD::Dup(events_fd);
close(events_fd);
@@ -170,7 +65,7 @@
}
// intented to run as cuttlefish host service
-[[noreturn]] void TcpServer() {
+void TcpServer() {
LOG(DEBUG) << "starting TCP server on " << FLAGS_tcp_port
<< " for vsock port " << FLAGS_vsock_port;
cuttlefish::SharedFD server;
@@ -184,10 +79,8 @@
CHECK(server->IsOpen()) << "Could not start server on " << FLAGS_tcp_port;
LOG(DEBUG) << "Accepting client connections";
int last_failure_reason = 0;
- while (true) {
- auto client_socket = cuttlefish::SharedFD::Accept(*server);
- CHECK(client_socket->IsOpen()) << "error creating client socket";
- cuttlefish::SharedFD vsock_socket = cuttlefish::SharedFD::VsockClient(
+ cuttlefish::Proxy(server, [&last_failure_reason]() {
+ auto vsock_socket = cuttlefish::SharedFD::VsockClient(
FLAGS_vsock_cid, FLAGS_vsock_port, SOCK_STREAM);
if (vsock_socket->IsOpen()) {
last_failure_reason = 0;
@@ -200,12 +93,9 @@
LOG(ERROR) << "Unable to connect to vsock server: "
<< vsock_socket->StrError();
}
- continue;
}
- auto thread = std::thread(HandleConnection, std::move(vsock_socket),
- std::move(client_socket));
- thread.detach();
- }
+ return vsock_socket;
+ });
}
cuttlefish::SharedFD OpenSocketConnection() {
@@ -232,7 +122,7 @@
}
// intended to run inside Android guest
-[[noreturn]] void VsockServer() {
+void VsockServer() {
LOG(DEBUG) << "Starting vsock server on " << FLAGS_vsock_port;
cuttlefish::SharedFD vsock;
if (FLAGS_server_fd < 0) {
@@ -248,17 +138,12 @@
close(FLAGS_server_fd);
}
CHECK(vsock->IsOpen()) << "Could not start server on " << FLAGS_vsock_port;
- while (true) {
- LOG(DEBUG) << "waiting for vsock connection";
- auto vsock_client = cuttlefish::SharedFD::Accept(*vsock);
- CHECK(vsock_client->IsOpen()) << "error creating vsock socket";
+ cuttlefish::Proxy(vsock, []() {
LOG(DEBUG) << "vsock socket accepted";
auto client = OpenSocketConnection();
CHECK(client->IsOpen()) << "error connecting to guest client";
- auto thread = std::thread(HandleConnection, std::move(vsock_client),
- std::move(client));
- thread.detach();
- }
+ return client;
+ });
}
} // namespace
diff --git a/common/libs/concurrency/multiplexer.h b/common/libs/concurrency/multiplexer.h
index 065c1a2..478963f 100644
--- a/common/libs/concurrency/multiplexer.h
+++ b/common/libs/concurrency/multiplexer.h
@@ -17,6 +17,7 @@
#pragma once
#include <condition_variable>
+#include <functional>
#include <memory>
#include <vector>
@@ -24,55 +25,64 @@
#include "common/libs/concurrency/thread_safe_queue.h"
namespace cuttlefish {
-namespace confui {
-template <typename T>
+template <typename T, typename Queue>
class Multiplexer {
public:
- Multiplexer(int n_qs, int max_elements) : sem_items_{0}, next_{0} {
- auto drop_new = [](typename ThreadSafeQueue<T>::QueueImpl* internal_q) {
- internal_q->pop_front();
- };
- for (int i = 0; i < n_qs; i++) {
- auto queue = std::make_unique<ThreadSafeQueue<T>>(max_elements, drop_new);
- queues_.push_back(std::move(queue));
- }
+ using QueuePtr = std::unique_ptr<Queue>;
+ using QueueSelector = std::function<int(void)>;
+
+ template <typename... Args>
+ static QueuePtr CreateQueue(Args&&... args) {
+ auto raw_ptr = new Queue(std::forward<Args>(args)...);
+ return QueuePtr(raw_ptr);
}
- int GetNewQueueId() {
- CHECK(next_ < queues_.size())
- << "can't get more queues than " << queues_.size();
- return next_++;
+ Multiplexer() : sem_items_{0} {}
+
+ int RegisterQueue(QueuePtr&& queue) {
+ const int id_to_return = queues_.size();
+ queues_.push_back(std::move(queue));
+ return id_to_return;
}
void Push(const int idx, T&& t) {
CheckIdx(idx);
- queues_[idx]->Push(t);
+ queues_[idx]->Push(std::move(t));
sem_items_.SemPost();
}
- T Pop() {
- // the idx must have an item!
- // no waiting in fn()!
- sem_items_.SemWait();
- for (auto& q : queues_) {
- if (q->IsEmpty()) {
- continue;
- }
- return q->Pop();
- }
- CHECK(false) << "Multiplexer.Pop() should be able to return an item";
- // must not reach here
- return T{};
+ T Pop(QueueSelector selector) {
+ SemWait();
+ int q_id = selector();
+ CheckIdx(q_id); // check, if weird, will die there
+ QueuePtr& queue = queues_[q_id];
+ CHECK(queue) << "queue must not be null.";
+ return queue->Pop();
}
+ T Pop() {
+ auto default_selector = [this]() -> int {
+ for (int i = 0; i < queues_.size(); i++) {
+ if (!queues_[i]->IsEmpty()) {
+ return i;
+ }
+ }
+ return -1;
+ };
+ return Pop(default_selector);
+ }
+
+ bool IsEmpty(const int idx) { return queues_[idx]->IsEmpty(); }
+
+ void SemWait() { sem_items_.SemWait(); }
+
private:
void CheckIdx(const int idx) {
CHECK(idx >= 0 && idx < queues_.size()) << "queues_ array out of bound";
}
// total items across the queues
Semaphore sem_items_;
- std::vector<std::unique_ptr<ThreadSafeQueue<T>>> queues_;
- int next_;
+ std::vector<QueuePtr> queues_;
+ QueuePtr null_ptr_;
};
-} // end of namespace confui
} // end of namespace cuttlefish
diff --git a/common/libs/concurrency/thread_safe_queue.h b/common/libs/concurrency/thread_safe_queue.h
index 9105299..ae16bef 100644
--- a/common/libs/concurrency/thread_safe_queue.h
+++ b/common/libs/concurrency/thread_safe_queue.h
@@ -20,6 +20,7 @@
#include <deque>
#include <iterator>
#include <mutex>
+#include <type_traits>
#include <utility>
namespace cuttlefish {
@@ -34,9 +35,11 @@
class ThreadSafeQueue {
public:
using QueueImpl = std::deque<T>;
+ using QueueFullHandler = std::function<void(QueueImpl*)>;
+
ThreadSafeQueue() = default;
explicit ThreadSafeQueue(std::size_t max_elements,
- std::function<void(QueueImpl*)> max_elements_handler)
+ QueueFullHandler max_elements_handler)
: max_elements_{max_elements},
max_elements_handler_{std::move(max_elements_handler)} {}
@@ -58,18 +61,17 @@
return std::move(items_);
}
- void Push(T&& t) {
+ template <typename U>
+ bool Push(U&& u) {
+ static_assert(std::is_assignable_v<T, decltype(u)>);
std::lock_guard<std::mutex> guard(m_);
- DropItemsIfAtCapacity();
- items_.push_back(std::move(t));
+ const bool has_room = DropItemsIfAtCapacity();
+ if (!has_room) {
+ return false;
+ }
+ items_.push_back(std::forward<U>(u));
new_item_.notify_one();
- }
-
- void Push(const T& t) {
- std::lock_guard<std::mutex> guard(m_);
- DropItemsIfAtCapacity();
- items_.push_back(t);
- new_item_.notify_one();
+ return true;
}
bool IsEmpty() {
@@ -83,15 +85,22 @@
}
private:
- void DropItemsIfAtCapacity() {
+ // return whether there's room to push
+ bool DropItemsIfAtCapacity() {
if (max_elements_ && max_elements_ == items_.size()) {
max_elements_handler_(&items_);
}
+ if (max_elements_ && max_elements_ == items_.size()) {
+ // handler intends to ignore the newly coming element or
+ // did not empty the room for whatever reason
+ return false;
+ }
+ return true;
}
std::mutex m_;
std::size_t max_elements_{};
- std::function<void(QueueImpl*)> max_elements_handler_{};
+ QueueFullHandler max_elements_handler_{};
std::condition_variable new_item_;
QueueImpl items_;
};
diff --git a/common/libs/confui/Android.bp b/common/libs/confui/Android.bp
index 13ff2fd..ca6348a 100644
--- a/common/libs/confui/Android.bp
+++ b/common/libs/confui/Android.bp
@@ -20,13 +20,16 @@
cc_library_static {
name: "libcuttlefish_confui",
srcs: [
+ "packet_types.cpp",
"packet.cpp",
+ "protocol_types.cpp",
"protocol.cpp",
],
static: {
static_libs: [
"libbase",
"libcuttlefish_fs",
+ "libteeui",
],
shared_libs: [
"libcrypto", // libcrypto_static is not accessible from all targets
diff --git a/common/libs/confui/confui.h b/common/libs/confui/confui.h
index 957fca1..18dec37 100644
--- a/common/libs/confui/confui.h
+++ b/common/libs/confui/confui.h
@@ -23,15 +23,5 @@
* header file(s)
*
*/
-#include "common/libs/confui/packet.h"
#include "common/libs/confui/protocol.h"
#include "common/libs/confui/utils.h"
-
-namespace cuttlefish {
-namespace confui {
-using packet::RecvConfUiMsg;
-using packet::SendAck;
-using packet::SendCmd;
-using packet::SendResponse;
-} // end of namespace confui
-} // end of namespace cuttlefish
diff --git a/common/libs/confui/packet.cpp b/common/libs/confui/packet.cpp
index d5be03a..debaeb8 100644
--- a/common/libs/confui/packet.cpp
+++ b/common/libs/confui/packet.cpp
@@ -16,82 +16,30 @@
#include "common/libs/confui/packet.h"
#include <algorithm>
-#include <iostream>
-
-#include <android-base/logging.h>
-#include <android-base/strings.h>
-
-#include "common/libs/confui/protocol.h"
-#include "common/libs/confui/utils.h"
-#include "common/libs/fs/shared_buf.h"
namespace cuttlefish {
namespace confui {
namespace packet {
-ConfUiMessage PayloadToConfUiMessage(const std::string& str_to_parse) {
- auto tokens = android::base::Split(str_to_parse, ":");
- ConfUiCheck(tokens.size() >= 3)
- << "PayloadToConfUiMessage takes \"" + str_to_parse + "\""
- << "and does not have 3 tokens";
- std::string msg;
- std::for_each(tokens.begin() + 2, tokens.end() - 1,
- [&msg](auto& token) { msg.append(token + ":"); });
- msg.append(*tokens.rbegin());
- return {tokens[0], tokens[1], msg};
-}
-
-// Use only this function to make a packet to send over the confirmation
-// ui packet layer
-template <typename... Args>
-static Payload ToPayload(const ConfUiCmd cmd, const std::string& session_id,
- Args&&... args) {
- std::string cmd_str = ToString(cmd);
- std::string msg =
- ArgsToString(session_id, ":", cmd_str, ":", std::forward<Args>(args)...);
- PayloadHeader header;
- header.payload_length_ = msg.size();
- return {header, msg};
-}
-
-template <typename... Args>
-static bool WritePayload(SharedFD d, const ConfUiCmd cmd,
- const std::string& session_id, Args&&... args) {
- if (!d->IsOpen()) {
- LOG(ERROR) << "file, socket, etc, is not open to write";
- return false;
- }
- auto [payload, msg] = ToPayload(cmd, session_id, std::forward<Args>(args)...);
-
- auto nwrite =
- WriteAll(d, reinterpret_cast<const char*>(&payload), sizeof(payload));
- if (nwrite != sizeof(payload)) {
- return false;
- }
- nwrite = cuttlefish::WriteAll(d, msg.c_str(), msg.size());
- if (nwrite != msg.size()) {
- return false;
- }
- return true;
-}
-
-static std::optional<ConfUiMessage> ReadPayload(SharedFD s) {
+static std::optional<std::vector<std::uint8_t>> ReadRawData(SharedFD s) {
if (!s->IsOpen()) {
- LOG(ERROR) << "file, socket, etc, is not open to read";
+ ConfUiLog(ERROR) << "file, socket, etc, is not open to read";
return std::nullopt;
}
- PayloadHeader p;
+ packet::PayloadHeader p;
auto nread = ReadExactBinary(s, &p);
if (nread != sizeof(p)) {
+ ConfUiLog(ERROR) << nread << " and sizeof(p) = " << sizeof(p)
+ << " not matching";
return std::nullopt;
}
-
if (p.payload_length_ == 0) {
- return {{SESSION_ANY, ToString(ConfUiCmd::kUnknown), std::string{""}}};
+ return {{}};
}
- if (p.payload_length_ >= kMaxPayloadLength) {
- LOG(ERROR) << "Payload length must be less than " << kMaxPayloadLength;
+ if (p.payload_length_ >= packet::kMaxPayloadLength) {
+ ConfUiLog(ERROR) << "Payload length must be less than "
+ << packet::kMaxPayloadLength;
return std::nullopt;
}
@@ -99,33 +47,109 @@
nread = ReadExact(s, buf.get(), p.payload_length_);
buf[p.payload_length_] = 0;
if (nread != p.payload_length_) {
+ ConfUiLog(ERROR) << "The length ReadRawData read does not match.";
return std::nullopt;
}
- std::string msg_to_parse{buf.get()};
- auto [session_id, type, contents] = PayloadToConfUiMessage(msg_to_parse);
- return {{session_id, type, contents}};
+ std::vector<std::uint8_t> result{buf.get(), buf.get() + nread};
+
+ return {result};
}
-std::optional<ConfUiMessage> RecvConfUiMsg(SharedFD fd) {
- return ReadPayload(fd);
+static std::optional<ParsedPacket> ParseRawData(
+ const std::vector<std::uint8_t>& data_to_parse) {
+ /*
+ * data_to_parse has 0 in it, so it is not exactly "your (text) std::string."
+ * If we type-cast data_to_parse to std::string and use 3rd party std::string-
+ * processing libraries, the outcome might be incorrect. However, the header
+ * part has no '\0' in it, and is actually a sequence of letters, or a text.
+ * So, we use android::base::Split() to take the header
+ *
+ */
+ std::string as_string{data_to_parse.begin(), data_to_parse.end()};
+ auto tokens = android::base::Split(as_string, ":");
+ CHECK(tokens.size() >= 3)
+ << "Raw packet for confirmation UI must have at least"
+ << " three components.";
+ /**
+ * Here is how the raw data, i.e. tokens[2:] looks like
+ *
+ * n:l[0]:l[1]:l[2]:...:l[n-1]:data[0]data[1]data[2]...data[n]
+ *
+ * Thus it basically has the number of items, the lengths of each item,
+ * and the byte representation of each item. n and l[i] are separated by ':'
+ * Note that the byte representation may have ':' in it. This could mess
+ * up the parsing if we totally depending on ':' separation.
+ *
+ * However, it is safe to assume that there's no ':' inside n or
+ * the string for l[i]. So, we do anyway split the data_to_parse by ':',
+ * and take n and from l[0] through l[n-1] only.
+ */
+ std::string session_id = tokens[0];
+ std::string cmd_type = tokens[1];
+ if (!IsOnlyDigits(tokens[2])) {
+ ConfUiLog(ERROR) << "Token[2] of the ConfUi packet should be a number";
+ return std::nullopt;
+ }
+ const int n = std::stoi(tokens[2]);
+
+ if (n + 2 > tokens.size()) {
+ ConfUiLog(ERROR) << "The ConfUi packet is ill-formatted.";
+ return std::nullopt;
+ }
+ ConfUiPacketInfo data_to_return;
+ std::vector<int> lengths;
+ for (int i = 1; i <= n; i++) {
+ if (!IsOnlyDigits(tokens[2 + i])) {
+ ConfUiLog(ERROR) << tokens[2 + i] << " should be a number but is not.";
+ return std::nullopt;
+ }
+ lengths.emplace_back(std::stoi(tokens[2 + i]));
+ }
+ // to find the first position of the non-header part
+ int pos = 0;
+ // 3 for three ":"s
+ pos += tokens[0].size() + tokens[1].size() + tokens[2].size() + 3;
+ for (int i = 1; i <= n; i++) {
+ pos += tokens[2 + i].size() + 1;
+ }
+ int expected_total_length = pos;
+ for (auto const len : lengths) {
+ expected_total_length += len;
+ }
+ if (expected_total_length != data_to_parse.size()) {
+ ConfUiLog(ERROR) << "expected length in ParseRawData is "
+ << expected_total_length << " while the actual length is "
+ << data_to_parse.size();
+ return std::nullopt;
+ }
+ for (const auto len : lengths) {
+ if (len == 0) {
+ // push null vector or whatever empty, appropriately-typed
+ // container
+ data_to_return.emplace_back(std::vector<std::uint8_t>{});
+ continue;
+ }
+ data_to_return.emplace_back(data_to_parse.begin() + pos,
+ data_to_parse.begin() + pos + len);
+ pos = pos + len;
+ }
+ ParsedPacket result{session_id, cmd_type, data_to_return};
+ return {result};
}
-bool SendCmd(SharedFD fd, const std::string& session_id, ConfUiCmd cmd,
- const std::string& additional_info) {
- return WritePayload(fd, cmd, session_id, additional_info);
+std::optional<ParsedPacket> ReadPayload(SharedFD s) {
+ auto raw_data = ReadRawData(s);
+ if (!raw_data) {
+ ConfUiLog(ERROR) << "raw data returned std::nullopt";
+ return std::nullopt;
+ }
+ auto parsed_result = ParseRawData(raw_data.value());
+ if (!parsed_result) {
+ ConfUiLog(ERROR) << "parsed result returns nullopt";
+ return std::nullopt;
+ }
+ return parsed_result;
}
-
-bool SendAck(SharedFD fd, const std::string& session_id, const bool is_success,
- const std::string& additional_info) {
- return WritePayload(fd, ConfUiCmd::kCliAck, session_id,
- ToCliAckMessage(is_success, additional_info));
-}
-
-bool SendResponse(SharedFD fd, const std::string& session_id,
- const std::string& additional_info) {
- return WritePayload(fd, ConfUiCmd::kCliRespond, session_id, additional_info);
-}
-
} // end of namespace packet
} // end of namespace confui
} // end of namespace cuttlefish
diff --git a/common/libs/confui/packet.h b/common/libs/confui/packet.h
index 848eb29..e109a61 100644
--- a/common/libs/confui/packet.h
+++ b/common/libs/confui/packet.h
@@ -15,56 +15,122 @@
#pragma once
+#include <algorithm>
#include <cstdint>
-#include <functional>
#include <optional>
#include <string>
#include <tuple>
+#include <type_traits>
+#include <vector>
-#include "common/libs/confui/protocol.h"
+#include <android-base/logging.h>
+#include <android-base/strings.h>
+
+#include "common/libs/confui/packet_types.h"
+#include "common/libs/confui/utils.h"
+#include "common/libs/fs/shared_buf.h"
#include "common/libs/fs/shared_fd.h"
+/**
+ * @file packet.h
+ *
+ * @brief lowest-level packet for communication between host & guest
+ *
+ * Each packet has three fields
+ * 1. session_id_: the name of the currently active confirmation UI session
+ * 2. type_: the type of command/response. E.g. start, stop, ack, abort, etc
+ * 3. additional_info_: all the other additional information
+ *
+ * The binary represenation of each packet is as follows:
+ * n:L[1]:L[2]:...:L[n]:data[1]data[2]data[3]...data[n]
+ *
+ * The additional_info_ is in general a variable number of items, each
+ * is either a byte vector (e.g. std::vector<uint8_t>) or a string.
+ *
+ * n is the number of items. L[i] is the length of i th item. data[i]
+ * is the binary representation of the i th item
+ *
+ */
namespace cuttlefish {
namespace confui {
namespace packet {
+
/*
- * for communication between Confirmation UI guest and host.
+ * methods in namespace impl is not intended for public use
*
- * Payload is actually the header. When we send/recv, besides Payload,
- * the "payload_length_" bytes should be additionally sent/recv'ed.
- *
- * The payload is assumed to be a text (e.g. char[N])
- * The WritePayload will create the string. When read, however,
- * the receiver should parse it
- *
- * The format we use for confirmation UI is:
- * session_id:type:contents
- *
- * e.g. GooglePay10354:start:my confirmaton message
+ * For exposed APIs, skip to "start of public APIs
+ * or, skip the namespace impl
*/
-struct PayloadHeader {
- std::uint32_t payload_length_;
-};
+namespace impl {
+template <typename Buffer, typename... Args>
+void AppendToBuffer(Buffer& buffer, Args&&... args) {
+ (buffer.insert(buffer.end(), std::begin(std::forward<Args>(args)),
+ std::end(std::forward<Args>(args))),
+ ...);
+}
-// PayloadHeader + the message actually being sent
-using Payload = std::tuple<PayloadHeader, std::string>;
+template <typename... Args>
+std::vector<int> MakeSizeHeader(Args&&... args) {
+ std::vector<int> lengths;
+ (lengths.push_back(std::distance(std::begin(args), std::end(args))), ...);
+ return lengths;
+}
-// msg will look like "334522:start:Hello I am Here!"
-// this function returns 334522, start, "Hello I am Here!"
-// if no session id is given, it is regarded as SESSION_ANY
-ConfUiMessage PayloadToConfUiMessage(const std::string& str_to_parse);
+// Use only this function to make a packet to send over the confirmation
+// ui packet layer
+template <typename... Args>
+Payload ToPayload(const std::string& cmd_str, const std::string& session_id,
+ Args&&... args) {
+ using namespace cuttlefish::confui::packet::impl;
+ constexpr auto n_args = sizeof...(Args);
+ std::stringstream ss;
+ ss << ArgsToString(session_id, ":", cmd_str, ":", n_args, ":");
+ // create size header
+ std::vector<int> size_info =
+ impl::MakeSizeHeader(std::forward<Args>(args)...);
+ for (const auto sz : size_info) {
+ ss << sz << ":";
+ }
+ std::string header = ss.str();
+ std::vector<std::uint8_t> payload_buffer{header.begin(), header.end()};
+ impl::AppendToBuffer(payload_buffer, std::forward<Args>(args)...);
-std::optional<ConfUiMessage> RecvConfUiMsg(SharedFD fd);
-bool SendAck(SharedFD fd, const std::string& session_id, const bool is_success,
- const std::string& additional_info);
-bool SendResponse(SharedFD fd, const std::string& session_id,
- const std::string& additional_info);
-// for HAL
-bool SendCmd(SharedFD fd, const std::string& session_id, ConfUiCmd cmd,
- const std::string& additional_info);
+ PayloadHeader ph;
+ ph.payload_length_ = payload_buffer.size();
+ return {ph, payload_buffer};
+}
+} // namespace impl
-// this is for short messages
-constexpr const ssize_t kMaxPayloadLength = 1000;
+/*
+ * start of public methods
+ */
+std::optional<ParsedPacket> ReadPayload(SharedFD s);
+
+template <typename... Args>
+bool WritePayload(SharedFD d, const std::string& cmd_str,
+ const std::string& session_id, Args&&... args) {
+ // TODO(kwstephenkim): type check Args... so that they are either
+ // kind of std::string or std::vector<1 byte>
+ if (!d->IsOpen()) {
+ ConfUiLog(ERROR) << "file, socket, etc, is not open to write";
+ return false;
+ }
+ auto [payload_header, data_to_send] =
+ impl::ToPayload(cmd_str, session_id, std::forward<Args>(args)...);
+ const std::string data_in_str(data_to_send.cbegin(), data_to_send.cend());
+
+ auto nwrite = WriteAll(d, reinterpret_cast<const char*>(&payload_header),
+ sizeof(payload_header));
+ if (nwrite != sizeof(payload_header)) {
+ return false;
+ }
+ nwrite = WriteAll(d, reinterpret_cast<const char*>(data_to_send.data()),
+ data_to_send.size());
+ if (nwrite != data_to_send.size()) {
+ return false;
+ }
+ return true;
+}
} // end of namespace packet
} // end of namespace confui
diff --git a/common/libs/confui/packet_types.cpp b/common/libs/confui/packet_types.cpp
new file mode 100644
index 0000000..f3108b5
--- /dev/null
+++ b/common/libs/confui/packet_types.cpp
@@ -0,0 +1,44 @@
+//
+// Copyright (C) 2021 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "common/libs/confui/packet_types.h"
+
+#include <sstream>
+
+namespace cuttlefish {
+namespace confui {
+namespace packet {
+std::string ToString(const ParsedPacket& packet) {
+ std::stringstream ss;
+ ss << "[" << packet.session_id_ << "," << packet.type_ << ",";
+ for (auto const& vec : packet.additional_info_) {
+ if (vec.empty()) {
+ ss << ",";
+ continue;
+ }
+ std::string token(vec.cbegin(), vec.cend());
+ ss << token << ",";
+ }
+ std::string result = ss.str();
+ bool is_remove_one_comma = (!packet.additional_info_.empty());
+ if (is_remove_one_comma) {
+ result.pop_back();
+ }
+ result.append("]");
+ return result;
+}
+} // end of namespace packet
+} // end of namespace confui
+} // end of namespace cuttlefish
diff --git a/common/libs/confui/packet_types.h b/common/libs/confui/packet_types.h
new file mode 100644
index 0000000..73f2745
--- /dev/null
+++ b/common/libs/confui/packet_types.h
@@ -0,0 +1,49 @@
+//
+// Copyright (C) 2021 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#pragma once
+
+#include <cstdint>
+#include <sstream>
+#include <string>
+#include <tuple>
+#include <vector>
+
+namespace cuttlefish {
+namespace confui {
+namespace packet {
+struct PayloadHeader {
+ std::uint32_t payload_length_;
+};
+
+using BufferType = std::vector<std::uint8_t>;
+
+// PayloadHeader + the byte size sent over the channel
+using Payload = std::tuple<PayloadHeader, BufferType>;
+
+// this is for short messages
+constexpr const ssize_t kMaxPayloadLength = 10000;
+
+using ConfUiPacketInfo = std::vector<std::vector<std::uint8_t>>;
+struct ParsedPacket {
+ std::string session_id_;
+ std::string type_;
+ ConfUiPacketInfo additional_info_;
+};
+
+std::string ToString(const ParsedPacket& packet);
+} // end of namespace packet
+} // end of namespace confui
+} // end of namespace cuttlefish
diff --git a/common/libs/confui/protocol.cpp b/common/libs/confui/protocol.cpp
index 6784034..03ca4cc 100644
--- a/common/libs/confui/protocol.cpp
+++ b/common/libs/confui/protocol.cpp
@@ -15,88 +15,259 @@
#include "common/libs/confui/protocol.h"
-#include <map>
#include <sstream>
-#include <unordered_map>
#include <vector>
#include <android-base/strings.h>
+#include "common/libs/confui/packet.h"
#include "common/libs/confui/utils.h"
+#include "common/libs/fs/shared_buf.h"
namespace cuttlefish {
namespace confui {
-std::string ToDebugString(const ConfUiCmd& cmd, const bool is_debug) {
- std::stringstream ss;
- ss << "of " << Enum2Base(cmd);
- std::string suffix = "";
- if (is_debug) {
- suffix.append(ss.str());
- }
- static std::unordered_map<ConfUiCmd, std::string> look_up_tab{
- {ConfUiCmd::kUnknown, "kUnknown"},
- {ConfUiCmd::kStart, "kStart"},
- {ConfUiCmd::kStop, "kStop"},
- {ConfUiCmd::kCliAck, "kCliAck"},
- {ConfUiCmd::kCliRespond, "kCliRespond"},
- {ConfUiCmd::kAbort, "kAbort"},
- {ConfUiCmd::kSuspend, "kSuspend"},
- {ConfUiCmd::kRestore, "kRestore"},
- {ConfUiCmd::kUserInputEvent, "kUserInputEvent"}};
- if (look_up_tab.find(cmd) != look_up_tab.end()) {
- return look_up_tab[cmd] + suffix;
- }
- return "kUnknown" + suffix;
+namespace {
+// default implementation of ToConfUiMessage
+template <ConfUiCmd C>
+std::unique_ptr<ConfUiMessage> ToConfUiMessage(
+ const packet::ParsedPacket& message) {
+ return std::make_unique<ConfUiGenericMessage<C>>(message.session_id_);
}
-std::string ToString(const ConfUiCmd& cmd) { return ToDebugString(cmd, false); }
+// these are specialized, and defined below
+template <>
+std::unique_ptr<ConfUiMessage> ToConfUiMessage<ConfUiCmd::kCliAck>(
+ const packet::ParsedPacket& message);
+template <>
+std::unique_ptr<ConfUiMessage> ToConfUiMessage<ConfUiCmd::kStart>(
+ const packet::ParsedPacket& message);
+template <>
+std::unique_ptr<ConfUiMessage> ToConfUiMessage<ConfUiCmd::kUserInputEvent>(
+ const packet::ParsedPacket& message);
+template <>
+std::unique_ptr<ConfUiMessage> ToConfUiMessage<ConfUiCmd::kUserTouchEvent>(
+ const packet::ParsedPacket& message);
+template <>
+std::unique_ptr<ConfUiMessage> ToConfUiMessage<ConfUiCmd::kCliRespond>(
+ const packet::ParsedPacket& message);
-ConfUiCmd ToCmd(std::uint32_t i) {
- std::vector<ConfUiCmd> all_cmds{
- ConfUiCmd::kStart, ConfUiCmd::kStop, ConfUiCmd::kCliAck,
- ConfUiCmd::kCliRespond, ConfUiCmd::kAbort, ConfUiCmd::kSuspend,
- ConfUiCmd::kRestore, ConfUiCmd::kUserInputEvent, ConfUiCmd::kUnknown};
+std::unique_ptr<ConfUiMessage> ToConfUiMessage(
+ const packet::ParsedPacket& confui_packet) {
+ const auto confui_cmd = ToCmd(confui_packet.type_);
+ switch (confui_cmd) {
+ // customized ConfUiMessage
+ case ConfUiCmd::kStart:
+ return ToConfUiMessage<ConfUiCmd::kStart>(confui_packet);
+ case ConfUiCmd::kCliAck:
+ return ToConfUiMessage<ConfUiCmd::kCliAck>(confui_packet);
+ case ConfUiCmd::kCliRespond:
+ return ToConfUiMessage<ConfUiCmd::kCliRespond>(confui_packet);
+ case ConfUiCmd::kUserInputEvent:
+ return ToConfUiMessage<ConfUiCmd::kUserInputEvent>(confui_packet);
+ case ConfUiCmd::kUserTouchEvent:
+ return ToConfUiMessage<ConfUiCmd::kUserTouchEvent>(confui_packet);
+ // default ConfUiMessage with session & type only
+ case ConfUiCmd::kAbort:
+ return ToConfUiMessage<ConfUiCmd::kAbort>(confui_packet);
+ case ConfUiCmd::kStop:
+ return ToConfUiMessage<ConfUiCmd::kStop>(confui_packet);
+ // these are errors
+ case ConfUiCmd::kUnknown:
+ default:
+ ConfUiLog(ERROR) << "ConfUiCmd value is not good for ToConfUiMessage: "
+ << ToString(confui_cmd);
+ break;
+ }
+ return {nullptr};
+}
+} // end of unnamed namespace
- for (auto& cmd : all_cmds) {
- if (i == Enum2Base(cmd)) {
- return cmd;
+std::string ToString(const ConfUiMessage& msg) { return msg.ToString(); }
+
+std::unique_ptr<ConfUiMessage> RecvConfUiMsg(SharedFD fd) {
+ if (!fd->IsOpen()) {
+ ConfUiLog(ERROR) << "file, socket, etc, is not open to read";
+ return {nullptr};
+ }
+ auto confui_packet_opt = packet::ReadPayload(fd);
+ if (!confui_packet_opt) {
+ ConfUiLog(ERROR) << "ReadPayload returns but with std::nullptr";
+ return {nullptr};
+ }
+
+ auto confui_packet = confui_packet_opt.value();
+ return ToConfUiMessage(confui_packet);
+}
+
+std::unique_ptr<ConfUiMessage> RecvConfUiMsg(const std::string& session_id,
+ SharedFD fd) {
+ auto conf_ui_msg = RecvConfUiMsg(fd);
+ if (!conf_ui_msg) {
+ return {nullptr};
+ }
+ auto recv_session_id = conf_ui_msg->GetSessionId();
+ if (session_id != recv_session_id) {
+ ConfUiLog(ERROR) << "Received Session ID (" << recv_session_id
+ << ") is not the expected one (" << session_id << ")";
+ return {nullptr};
+ }
+ return conf_ui_msg;
+}
+
+bool SendAbortCmd(SharedFD fd, const std::string& session_id) {
+ ConfUiGenericMessage<ConfUiCmd::kAbort> confui_msg{session_id};
+ return confui_msg.SendOver(fd);
+}
+
+bool SendStopCmd(SharedFD fd, const std::string& session_id) {
+ ConfUiGenericMessage<ConfUiCmd::kStop> confui_msg{session_id};
+ return confui_msg.SendOver(fd);
+}
+
+bool SendAck(SharedFD fd, const std::string& session_id, const bool is_success,
+ const std::string& status_message) {
+ ConfUiAckMessage confui_msg{session_id, is_success, status_message};
+ return confui_msg.SendOver(fd);
+}
+
+bool SendResponse(SharedFD fd, const std::string& session_id,
+ const UserResponse::type& plain_selection,
+ const std::vector<std::uint8_t>& signed_response,
+ const std::vector<std::uint8_t>& message) {
+ ConfUiCliResponseMessage confui_msg{session_id, plain_selection,
+ signed_response, message};
+ return confui_msg.SendOver(fd);
+}
+
+bool SendStartCmd(SharedFD fd, const std::string& session_id,
+ const std::string& prompt_text,
+ const std::vector<std::uint8_t>& extra_data,
+ const std::string& locale,
+ const std::vector<teeui::UIOption>& ui_opts) {
+ ConfUiStartMessage confui_msg{session_id, prompt_text, extra_data, locale,
+ ui_opts};
+ return confui_msg.SendOver(fd);
+}
+
+// this is only for deliverSecureInputEvent
+bool SendUserSelection(SharedFD fd, const std::string& session_id,
+ const UserResponse::type& confirm_cancel) {
+ ConfUiUserSelectionMessage confui_msg{session_id, confirm_cancel};
+ return confui_msg.SendOver(fd);
+}
+
+// specialized ToConfUiMessage()
+namespace {
+template <>
+std::unique_ptr<ConfUiMessage> ToConfUiMessage<ConfUiCmd::kCliAck>(
+ const packet::ParsedPacket& message) {
+ auto type = ToCmd(message.type_);
+ auto& contents = message.additional_info_;
+ if (type != ConfUiCmd::kCliAck) {
+ ConfUiLog(ERROR) << "Received cmd is not ack but " << ToString(type);
+ return {nullptr};
+ }
+
+ if (contents.size() != 2) {
+ ConfUiLog(ERROR)
+ << "Ack message should only have pass/fail and a status message";
+ return {nullptr};
+ }
+
+ const std::string success_str(contents[0].begin(), contents[0].end());
+ const bool is_success = (success_str == "success");
+ const std::string status_message(contents[1].begin(), contents[1].end());
+ return std::make_unique<ConfUiAckMessage>(message.session_id_, is_success,
+ status_message);
+}
+
+template <>
+std::unique_ptr<ConfUiMessage> ToConfUiMessage<ConfUiCmd::kStart>(
+ const packet::ParsedPacket& message) {
+ /*
+ * additional_info_[0]: prompt text
+ * additional_info_[1]: extra data
+ * additional_info_[2]: locale
+ * additional_info_[3]: UIOptions
+ *
+ */
+ if (message.additional_info_.size() < 3) {
+ ConfUiLog(ERROR) << "ConfUiMessage for kStart is ill-formatted: "
+ << packet::ToString(message);
+ return {nullptr};
+ }
+ std::vector<teeui::UIOption> ui_opts;
+ bool has_ui_option = (message.additional_info_.size() == 4) &&
+ !(message.additional_info_[3].empty());
+ if (has_ui_option) {
+ std::string ui_opts_string{message.additional_info_[3].begin(),
+ message.additional_info_[3].end()};
+ auto tokens = android::base::Split(ui_opts_string, ",");
+ for (auto token : tokens) {
+ auto ui_opt_optional = ToUiOption(token);
+ if (!ui_opt_optional) {
+ ConfUiLog(ERROR) << "Wrong UiOption String : " << token;
+ return {nullptr};
+ }
+ ui_opts.emplace_back(ui_opt_optional.value());
}
}
- return ConfUiCmd::kUnknown;
+ auto sm = std::make_unique<ConfUiStartMessage>(
+ message.session_id_,
+ std::string(message.additional_info_[0].begin(),
+ message.additional_info_[0].end()),
+ message.additional_info_[1],
+ std::string(message.additional_info_[2].begin(),
+ message.additional_info_[2].end()),
+ ui_opts);
+ return sm;
}
-ConfUiCmd ToCmd(const std::string& cmd_str) {
- static std::map<std::string, ConfUiCmd> cmds = {
- {"kStart", ConfUiCmd::kStart},
- {"kStop", ConfUiCmd::kStop},
- {"kCliAck", ConfUiCmd::kCliAck},
- {"kCliRespond", ConfUiCmd::kCliRespond},
- {"kAbort", ConfUiCmd::kAbort},
- {"kSuspend", ConfUiCmd::kSuspend},
- {"kRestore", ConfUiCmd::kRestore},
- {"kUserInputEvent", ConfUiCmd::kUserInputEvent},
- };
- if (cmds.find(cmd_str) != cmds.end()) {
- return cmds[cmd_str];
+template <>
+std::unique_ptr<ConfUiMessage> ToConfUiMessage<ConfUiCmd::kUserInputEvent>(
+ const packet::ParsedPacket& message) {
+ if (message.additional_info_.size() < 1) {
+ ConfUiLog(ERROR)
+ << "kUserInputEvent message should have at least one additional_info_";
+ return {nullptr};
}
- return ConfUiCmd::kUnknown;
+ auto response = std::string{message.additional_info_[0].begin(),
+ message.additional_info_[0].end()};
+ return std::make_unique<ConfUiUserSelectionMessage>(message.session_id_,
+ response);
}
-std::string ToCliAckMessage(const bool is_success, const std::string& message) {
- std::string header = "error:";
- if (is_success) {
- header = "success:";
+template <>
+std::unique_ptr<ConfUiMessage> ToConfUiMessage<ConfUiCmd::kUserTouchEvent>(
+ const packet::ParsedPacket& message) {
+ if (message.additional_info_.size() < 2) {
+ ConfUiLog(ERROR)
+ << "kUserTouchEvent message should have at least two additional_info_";
+ return {nullptr};
}
- return header + message;
+ auto x = std::string(message.additional_info_[0].begin(),
+ message.additional_info_[0].end());
+ auto y = std::string(message.additional_info_[1].begin(),
+ message.additional_info_[1].end());
+ return std::make_unique<ConfUiUserTouchMessage>(message.session_id_,
+ std::stoi(x), std::stoi(y));
}
-std::string ToCliAckSuccessMsg(const std::string& message) {
- return ToCliAckMessage(true, message);
+template <>
+std::unique_ptr<ConfUiMessage> ToConfUiMessage<ConfUiCmd::kCliRespond>(
+ const packet::ParsedPacket& message) {
+ if (message.additional_info_.size() < 3) {
+ ConfUiLog(ERROR)
+ << "kCliRespond message should have at least two additional info";
+ return {nullptr};
+ }
+ auto response = std::string{message.additional_info_[0].begin(),
+ message.additional_info_[0].end()};
+ auto sign = message.additional_info_[1];
+ auto msg = message.additional_info_[2];
+ return std::make_unique<ConfUiCliResponseMessage>(message.session_id_,
+ response, sign, msg);
}
-
-std::string ToCliAckErrorMsg(const std::string& message) {
- return ToCliAckMessage(false, message);
-}
-
+} // end of unnamed namespace
} // end of namespace confui
} // end of namespace cuttlefish
diff --git a/common/libs/confui/protocol.h b/common/libs/confui/protocol.h
index 582b43e..f41afc4 100644
--- a/common/libs/confui/protocol.h
+++ b/common/libs/confui/protocol.h
@@ -16,48 +16,55 @@
#pragma once
#include <cstdint>
+#include <optional>
#include <string>
+#include <tuple>
+
+#include <teeui/common_message_types.h> // /system/teeui/libteeui/.../include
+
+#include "common/libs/confui/packet_types.h"
+#include "common/libs/confui/protocol_types.h"
+#include "common/libs/fs/shared_fd.h"
namespace cuttlefish {
namespace confui {
-// When you update this, please update all the utility functions
-// in conf.cpp: e.g. ToString, etc
-enum class ConfUiCmd : std::uint32_t {
- kUnknown = 100,
- kStart = 111, // start rendering, send confirmation msg, & wait respond
- kStop = 112, // start rendering, send confirmation msg, & wait respond
- kCliAck = 113, // client acknowledged. "error:err_msg" or "success:command"
- kCliRespond = 114, // with "confirm" or "cancel"
- kAbort = 115, // to abort the current session
- kSuspend = 116, // to suspend, so do save the context
- kRestore = 117,
- kUserInputEvent = 200
-};
-std::string ToString(const ConfUiCmd& cmd);
-std::string ToDebugString(const ConfUiCmd& cmd, const bool is_debug);
-ConfUiCmd ToCmd(const std::string& cmd_str);
-ConfUiCmd ToCmd(std::uint32_t i);
+std::string ToString(const ConfUiMessage& msg);
-struct UserResponse {
- using type = std::string;
- constexpr static const auto kConfirm = "user_confirm";
- constexpr static const auto kCancel = "user_cancel";
- constexpr static const auto kUnknown = "user_unknown";
-};
+constexpr auto SESSION_ANY = "";
+/*
+ * received confirmation UI message on the guest could be abort or
+ * ack/response. Thus, the guest APIs should call RecvConfUiMsg(fd),
+ * see which is it, and then use Into*(conf_ui_message) to
+ * parse & use it.
+ *
+ */
+std::unique_ptr<ConfUiMessage> RecvConfUiMsg(SharedFD fd);
+std::unique_ptr<ConfUiMessage> RecvConfUiMsg(const std::string& session_id,
+ SharedFD fd);
-// invalid/ignored session id
-constexpr char SESSION_ANY[] = "";
+bool SendAbortCmd(SharedFD fd, const std::string& session_id);
-std::string ToCliAckMessage(const bool is_success, const std::string& message);
-std::string ToCliAckErrorMsg(const std::string& message);
-std::string ToCliAckSuccessMsg(const std::string& message);
+bool SendAck(SharedFD fd, const std::string& session_id, const bool is_success,
+ const std::string& status_message);
+bool SendResponse(SharedFD fd, const std::string& session_id,
+ const UserResponse::type& plain_selection,
+ const std::vector<std::uint8_t>& signed_response,
+ // signing is a function of message, key
+ const std::vector<std::uint8_t>& message);
-struct ConfUiMessage {
- std::string session_id_;
- std::string type_; // cmd, which cmd? ack, response, etc
- std::string msg_;
-};
+// for HAL
+bool SendStartCmd(SharedFD fd, const std::string& session_id,
+ const std::string& prompt_text,
+ const std::vector<std::uint8_t>& extra_data,
+ const std::string& locale,
+ const std::vector<teeui::UIOption>& ui_opts);
+
+bool SendStopCmd(SharedFD fd, const std::string& session_id);
+
+// for HAL::deliverSecureInputEvent
+bool SendUserSelection(SharedFD fd, const std::string& session_id,
+ const UserResponse::type& confirm_cancel);
} // end of namespace confui
} // end of namespace cuttlefish
diff --git a/common/libs/confui/protocol_types.cpp b/common/libs/confui/protocol_types.cpp
new file mode 100644
index 0000000..8c293ea
--- /dev/null
+++ b/common/libs/confui/protocol_types.cpp
@@ -0,0 +1,174 @@
+//
+// Copyright (C) 2021 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "common/libs/confui/protocol_types.h"
+
+#include <map>
+#include <sstream>
+#include <unordered_map>
+
+#include "common/libs/confui/packet.h"
+#include "common/libs/confui/utils.h"
+
+namespace cuttlefish {
+namespace confui {
+std::string ToDebugString(const ConfUiCmd& cmd, const bool is_verbose) {
+ std::stringstream ss;
+ ss << " of " << Enum2Base(cmd);
+ std::string suffix = "";
+ if (is_verbose) {
+ suffix.append(ss.str());
+ }
+ static std::unordered_map<ConfUiCmd, std::string> look_up_tab{
+ {ConfUiCmd::kUnknown, "kUnknown"},
+ {ConfUiCmd::kStart, "kStart"},
+ {ConfUiCmd::kStop, "kStop"},
+ {ConfUiCmd::kCliAck, "kCliAck"},
+ {ConfUiCmd::kCliRespond, "kCliRespond"},
+ {ConfUiCmd::kAbort, "kAbort"},
+ {ConfUiCmd::kUserInputEvent, "kUserInputEvent"},
+ {ConfUiCmd::kUserInputEvent, "kUserTouchEvent"}};
+ if (look_up_tab.find(cmd) != look_up_tab.end()) {
+ return look_up_tab[cmd] + suffix;
+ }
+ return "kUnknown" + suffix;
+}
+
+std::string ToString(const ConfUiCmd& cmd) { return ToDebugString(cmd, false); }
+
+ConfUiCmd ToCmd(std::uint32_t i) {
+ std::vector<ConfUiCmd> all_cmds{
+ ConfUiCmd::kStart, ConfUiCmd::kStop,
+ ConfUiCmd::kCliAck, ConfUiCmd::kCliRespond,
+ ConfUiCmd::kAbort, ConfUiCmd::kUserInputEvent,
+ ConfUiCmd::kUserTouchEvent, ConfUiCmd::kUnknown};
+
+ for (auto& cmd : all_cmds) {
+ if (i == Enum2Base(cmd)) {
+ return cmd;
+ }
+ }
+ return ConfUiCmd::kUnknown;
+}
+
+ConfUiCmd ToCmd(const std::string& cmd_str) {
+ static std::map<std::string, ConfUiCmd> cmds = {
+ {"kStart", ConfUiCmd::kStart},
+ {"kStop", ConfUiCmd::kStop},
+ {"kCliAck", ConfUiCmd::kCliAck},
+ {"kCliRespond", ConfUiCmd::kCliRespond},
+ {"kAbort", ConfUiCmd::kAbort},
+ {"kUserInputEvent", ConfUiCmd::kUserInputEvent},
+ {"kUserTouchEvent", ConfUiCmd::kUserTouchEvent},
+ };
+ if (cmds.find(cmd_str) != cmds.end()) {
+ return cmds[cmd_str];
+ }
+ return ConfUiCmd::kUnknown;
+}
+
+std::string ToString(const teeui::UIOption ui_opt) {
+ return std::to_string(static_cast<int>(ui_opt));
+}
+
+std::optional<teeui::UIOption> ToUiOption(const std::string& src) {
+ if (!IsOnlyDigits(src)) {
+ return std::nullopt;
+ }
+ return {static_cast<teeui::UIOption>(std::stoi(src))};
+}
+
+template <typename T>
+static std::string ByteVecToString(const std::vector<T>& v) {
+ static_assert(sizeof(T) == 1);
+ std::string result{v.begin(), v.end()};
+ return result;
+}
+
+bool ConfUiMessage::IsUserInput() const {
+ switch (GetType()) {
+ case ConfUiCmd::kUserInputEvent:
+ case ConfUiCmd::kUserTouchEvent:
+ return true;
+ default:
+ return false;
+ }
+}
+
+std::string ConfUiAckMessage::ToString() const {
+ return CreateString(session_id_, confui::ToString(GetType()),
+ (is_success_ ? "success" : "fail"), status_message_);
+}
+
+bool ConfUiAckMessage::SendOver(SharedFD fd) {
+ return Send_(fd, GetType(), session_id_,
+ std::string(is_success_ ? "success" : "fail"), status_message_);
+}
+
+std::string ConfUiCliResponseMessage::ToString() const {
+ return CreateString(session_id_, confui::ToString(GetType()), response_,
+ ByteVecToString(sign_), ByteVecToString(message_));
+}
+
+bool ConfUiCliResponseMessage::SendOver(SharedFD fd) {
+ return Send_(fd, GetType(), session_id_, response_, sign_, message_);
+}
+
+std::string ConfUiStartMessage::UiOptsToString() const {
+ std::stringstream ss;
+ for (const auto& ui_opt : ui_opts_) {
+ ss << cuttlefish::confui::ToString(ui_opt) << ",";
+ }
+ auto ui_opt_str = ss.str();
+ if (!ui_opt_str.empty()) {
+ ui_opt_str.pop_back();
+ }
+ return ui_opt_str;
+}
+
+std::string ConfUiStartMessage::ToString() const {
+ auto ui_opts_str = UiOptsToString();
+ return CreateString(
+ session_id_, confui::ToString(GetType()), prompt_text_, locale_,
+ std::string(extra_data_.begin(), extra_data_.end()), ui_opts_str);
+}
+
+bool ConfUiStartMessage::SendOver(SharedFD fd) {
+ return Send_(fd, GetType(), session_id_, prompt_text_, extra_data_, locale_,
+ UiOptsToString());
+}
+
+std::string ConfUiUserSelectionMessage::ToString() const {
+ return CreateString(session_id_, confui::ToString(GetType()), response_);
+}
+
+bool ConfUiUserSelectionMessage::SendOver(SharedFD fd) {
+ return Send_(fd, GetType(), session_id_, response_);
+}
+
+std::string ConfUiUserTouchMessage::ToString() const {
+ std::stringstream ss;
+ ss << "(" << x_ << "," << y_ << ")";
+ auto pos = ss.str();
+ return CreateString(session_id_, confui::ToString(GetType()), response_, pos);
+}
+
+bool ConfUiUserTouchMessage::SendOver(SharedFD fd) {
+ return Send_(fd, GetType(), session_id_, std::to_string(x_),
+ std::to_string(y_));
+}
+
+} // end of namespace confui
+} // end of namespace cuttlefish
diff --git a/common/libs/confui/protocol_types.h b/common/libs/confui/protocol_types.h
new file mode 100644
index 0000000..98f581f
--- /dev/null
+++ b/common/libs/confui/protocol_types.h
@@ -0,0 +1,232 @@
+//
+// Copyright (C) 2021 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#pragma once
+
+#include <cstdint>
+#include <string>
+#include <utility>
+#include <vector>
+
+#include <teeui/common_message_types.h> // /system/teeui/libteeui/.../include
+
+#include "common/libs/confui/packet.h"
+#include "common/libs/confui/packet_types.h"
+
+#include "common/libs/confui/utils.h"
+#include "common/libs/fs/shared_fd.h"
+
+namespace cuttlefish {
+namespace confui {
+// When you update this, please update all the utility functions
+// in conf.cpp: e.g. ToString, etc
+enum class ConfUiCmd : std::uint32_t {
+ kUnknown = 100,
+ kStart = 111, // start rendering, send confirmation msg, & wait respond
+ kStop = 112, // start rendering, send confirmation msg, & wait respond
+ kCliAck = 113, // client acknowledged. "error:err_msg" or "success:command"
+ kCliRespond = 114, // with "confirm" or "cancel" or "abort"
+ kAbort = 115, // to abort the current session
+ kUserInputEvent = 200,
+ kUserTouchEvent = 201
+};
+
+// this is for short messages
+constexpr const ssize_t kMaxMessageLength = packet::kMaxPayloadLength;
+
+std::string ToString(const ConfUiCmd& cmd);
+std::string ToDebugString(const ConfUiCmd& cmd, const bool is_debug);
+ConfUiCmd ToCmd(const std::string& cmd_str);
+ConfUiCmd ToCmd(std::uint32_t i);
+
+std::string ToString(const teeui::UIOption ui_opt);
+std::optional<teeui::UIOption> ToUiOption(const std::string&);
+
+struct HostError {
+ static constexpr char kSystemError[] = "system_error";
+ static constexpr char kUIError[] = "ui_error";
+ static constexpr char kMessageTooLongError[] = "msg_too_long_error";
+ static constexpr char kIncorrectUTF8[] = "msg_incorrect_utf8";
+};
+
+struct UserResponse {
+ using type = std::string;
+ constexpr static const auto kConfirm = "user_confirm";
+ constexpr static const auto kCancel = "user_cancel";
+ constexpr static const auto kTouchEvent = "user_touch";
+ // user may close x button on the virtual window or so
+ // or.. scroll the session up and throw to trash bin
+ constexpr static const auto kUserAbort = "user_abort";
+ constexpr static const auto kUnknown = "user_unknown";
+};
+
+class ConfUiMessage {
+ public:
+ ConfUiMessage(const std::string& session_id) : session_id_{session_id} {}
+ virtual ~ConfUiMessage() = default;
+ virtual std::string ToString() const = 0;
+ void SetSessionId(const std::string session_id) { session_id_ = session_id; }
+ std::string GetSessionId() const { return session_id_; }
+ virtual ConfUiCmd GetType() const = 0;
+ virtual bool SendOver(SharedFD fd) = 0;
+ bool IsUserInput() const;
+
+ protected:
+ std::string session_id_;
+ template <typename... Args>
+ static std::string CreateString(Args&&... args) {
+ return "[" + ArgsToStringWithDelim(",", std::forward<Args>(args)...) + "]";
+ }
+ template <typename... Args>
+ static bool Send_(SharedFD fd, const ConfUiCmd cmd,
+ const std::string& session_id, Args&&... args) {
+ return packet::WritePayload(fd, confui::ToString(cmd), session_id,
+ std::forward<Args>(args)...);
+ }
+};
+
+template <ConfUiCmd cmd>
+class ConfUiGenericMessage : public ConfUiMessage {
+ public:
+ ConfUiGenericMessage(const std::string& session_id)
+ : ConfUiMessage{session_id} {}
+ virtual ~ConfUiGenericMessage() = default;
+ std::string ToString() const override {
+ return CreateString(session_id_, confui::ToString(GetType()));
+ }
+ ConfUiCmd GetType() const override { return cmd; }
+ bool SendOver(SharedFD fd) override {
+ return Send_(fd, GetType(), session_id_);
+ }
+};
+
+class ConfUiAckMessage : public ConfUiMessage {
+ public:
+ ConfUiAckMessage(const std::string& session_id, const bool is_success,
+ const std::string& status)
+ : ConfUiMessage{session_id},
+ is_success_(is_success),
+ status_message_(status) {}
+ virtual ~ConfUiAckMessage() = default;
+ std::string ToString() const override;
+ ConfUiCmd GetType() const override { return ConfUiCmd::kCliAck; }
+ bool SendOver(SharedFD fd) override;
+ bool IsSuccess() const { return is_success_; }
+ std::string GetStatusMessage() const { return status_message_; }
+
+ private:
+ bool is_success_;
+ std::string status_message_;
+};
+
+// the signed user response sent to the guest
+class ConfUiCliResponseMessage : public ConfUiMessage {
+ public:
+ ConfUiCliResponseMessage(const std::string& session_id,
+ const UserResponse::type& response,
+ const std::vector<std::uint8_t>& sign = {},
+ const std::vector<std::uint8_t>& msg = {})
+ : ConfUiMessage(session_id),
+ response_(response),
+ sign_(sign),
+ message_{msg} {}
+ virtual ~ConfUiCliResponseMessage() = default;
+ std::string ToString() const override;
+ ConfUiCmd GetType() const override { return ConfUiCmd::kCliRespond; }
+ auto GetResponse() const { return response_; }
+ auto GetMessage() const { return message_; }
+ auto GetSign() const { return sign_; }
+ bool SendOver(SharedFD fd) override;
+
+ private:
+ UserResponse::type response_; // plain format
+ std::vector<std::uint8_t> sign_; // signed format
+ // second argument to pass via resultCB of promptUserConfirmation
+ std::vector<std::uint8_t> message_;
+};
+
+class ConfUiStartMessage : public ConfUiMessage {
+ public:
+ ConfUiStartMessage(const std::string session_id,
+ const std::string& prompt_text = "",
+ const std::vector<std::uint8_t>& extra_data = {},
+ const std::string& locale = "C",
+ const std::vector<teeui::UIOption> ui_opts = {})
+ : ConfUiMessage(session_id),
+ prompt_text_(prompt_text),
+ extra_data_(extra_data),
+ locale_(locale),
+ ui_opts_(ui_opts) {}
+ virtual ~ConfUiStartMessage() = default;
+ std::string ToString() const override;
+ ConfUiCmd GetType() const override { return ConfUiCmd::kStart; }
+ std::string GetPromptText() const { return prompt_text_; }
+ std::vector<std::uint8_t> GetExtraData() const { return extra_data_; }
+ std::string GetLocale() const { return locale_; }
+ std::vector<teeui::UIOption> GetUiOpts() const { return ui_opts_; }
+ bool SendOver(SharedFD fd) override;
+
+ private:
+ std::string prompt_text_;
+ std::vector<std::uint8_t> extra_data_;
+ std::string locale_;
+ std::vector<teeui::UIOption> ui_opts_;
+
+ std::string UiOptsToString() const;
+};
+
+// this one is for deliverSecureInputEvent() as well as
+// physical-input based implementation
+class ConfUiUserSelectionMessage : public ConfUiMessage {
+ public:
+ ConfUiUserSelectionMessage(const std::string& session_id,
+ const UserResponse::type& response)
+ : ConfUiMessage(session_id), response_(response) {}
+ virtual ~ConfUiUserSelectionMessage() = default;
+ std::string ToString() const override;
+ ConfUiCmd GetType() const override { return ConfUiCmd::kUserInputEvent; }
+ auto GetResponse() const { return response_; }
+ bool SendOver(SharedFD fd) override;
+
+ private:
+ UserResponse::type response_;
+};
+
+class ConfUiUserTouchMessage : public ConfUiMessage {
+ public:
+ ConfUiUserTouchMessage(const std::string& session_id, const int x,
+ const int y)
+ : ConfUiMessage(session_id),
+ x_(x),
+ y_(y),
+ response_(UserResponse::kTouchEvent) {}
+ virtual ~ConfUiUserTouchMessage() = default;
+ std::string ToString() const override;
+ ConfUiCmd GetType() const override { return ConfUiCmd::kUserTouchEvent; }
+ auto GetResponse() const { return response_; }
+ bool SendOver(SharedFD fd) override;
+ std::pair<int, int> GetLocation() { return {x_, y_}; }
+
+ private:
+ int x_;
+ int y_;
+ UserResponse::type response_;
+};
+
+using ConfUiAbortMessage = ConfUiGenericMessage<ConfUiCmd::kAbort>;
+using ConfUiStopMessage = ConfUiGenericMessage<ConfUiCmd::kStop>;
+
+} // end of namespace confui
+} // end of namespace cuttlefish
diff --git a/common/libs/confui/utils.h b/common/libs/confui/utils.h
index 2e4ac04..1f3814f 100644
--- a/common/libs/confui/utils.h
+++ b/common/libs/confui/utils.h
@@ -15,6 +15,7 @@
#pragma once
+#include <algorithm>
#include <sstream>
#include <string>
#include <type_traits>
@@ -35,7 +36,14 @@
std::string ArgsToStringWithDelim(Delim&& delim, Args&&... args) {
std::stringstream ss;
([&ss, &delim](auto& arg) { ss << arg << delim; }(args), ...);
- return ss.str();
+ auto result = ss.str();
+ std::string delim_str(delim);
+ if (!result.empty() && !delim_str.empty()) {
+ for (int i = 0; i < delim_str.size(); i++) {
+ result.pop_back();
+ }
+ }
+ return result;
}
// make t... to a single string with no blank in between
@@ -44,6 +52,11 @@
return ArgsToStringWithDelim("", std::forward<Args>(args)...);
}
+inline bool IsOnlyDigits(const std::string& src) {
+ return std::all_of(src.begin(), src.end(),
+ [](int c) -> bool { return std::isdigit(c); });
+}
+
// note that no () surrounding LOG(level) << "ConfUI:" is crucial
#define ConfUiLog(LOG_LEVEL) LOG(LOG_LEVEL) << "ConfUI: "
diff --git a/common/libs/fs/Android.bp b/common/libs/fs/Android.bp
index c140e0d..d038e0c 100644
--- a/common/libs/fs/Android.bp
+++ b/common/libs/fs/Android.bp
@@ -20,6 +20,7 @@
cc_library {
name: "libcuttlefish_fs",
srcs: [
+ "epoll.cpp",
"shared_buf.cc",
"shared_fd.cpp",
"shared_fd_stream.cpp",
@@ -49,6 +50,7 @@
cc_library_static {
name: "libcuttlefish_fs_product",
srcs: [
+ "epoll.cpp",
"shared_buf.cc",
"shared_fd.cpp",
"shared_fd_stream.cpp",
diff --git a/common/libs/fs/epoll.cpp b/common/libs/fs/epoll.cpp
new file mode 100644
index 0000000..509d3df
--- /dev/null
+++ b/common/libs/fs/epoll.cpp
@@ -0,0 +1,178 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "common/libs/fs/epoll.h"
+
+#include <sys/epoll.h>
+
+#include <memory>
+#include <optional>
+#include <set>
+#include <shared_mutex>
+
+#include "common/libs/fs/shared_fd.h"
+#include "common/libs/utils/result.h"
+
+namespace cuttlefish {
+
+Result<Epoll> Epoll::Create() {
+ int fd = epoll_create1(EPOLL_CLOEXEC);
+ if (fd == -1) {
+ return CF_ERRNO("Failed to create epoll");
+ }
+ SharedFD shared{std::shared_ptr<FileInstance>(new FileInstance(fd, 0))};
+ return Epoll(shared);
+}
+
+Epoll::Epoll() = default;
+
+Epoll::Epoll(SharedFD epoll_fd) : epoll_fd_(epoll_fd) {}
+
+Epoll::Epoll(Epoll&& other) {
+ std::unique_lock own_watched(watched_mutex_, std::defer_lock);
+ std::unique_lock own_epoll(epoll_mutex_, std::defer_lock);
+ std::unique_lock other_epoll(other.epoll_mutex_, std::defer_lock);
+ std::unique_lock other_watched(other.watched_mutex_, std::defer_lock);
+ std::lock(own_watched, own_epoll, other_epoll, other_watched);
+
+ epoll_fd_ = std::move(other.epoll_fd_);
+ watched_ = std::move(other.watched_);
+}
+
+Epoll& Epoll::operator=(Epoll&& other) {
+ std::unique_lock own_watched(watched_mutex_, std::defer_lock);
+ std::unique_lock own_epoll(epoll_mutex_, std::defer_lock);
+ std::unique_lock other_epoll(other.epoll_mutex_, std::defer_lock);
+ std::unique_lock other_watched(other.watched_mutex_, std::defer_lock);
+ std::lock(own_watched, own_epoll, other_epoll, other_watched);
+
+ epoll_fd_ = std::move(other.epoll_fd_);
+ watched_ = std::move(other.watched_);
+ return *this;
+}
+
+Result<void> Epoll::Add(SharedFD fd, uint32_t events) {
+ std::unique_lock watched_lock(watched_mutex_, std::defer_lock);
+ std::shared_lock epoll_lock(epoll_mutex_, std::defer_lock);
+ std::lock(watched_lock, epoll_lock);
+ CF_EXPECT(epoll_fd_->IsOpen(), "Empty Epoll instance");
+
+ if (watched_.count(fd) != 0) {
+ return CF_ERRNO("Watched set already contains fd");
+ }
+ epoll_event event;
+ event.events = events;
+ event.data.fd = fd->fd_;
+ int success = epoll_ctl(epoll_fd_->fd_, EPOLL_CTL_ADD, fd->fd_, &event);
+ if (success != 0 && errno == EEXIST) {
+ // We're already tracking this fd, don't drop it from the set.
+ return CF_ERRNO("epoll_ctl: File descriptor was already present");
+ } else if (success != 0) {
+ return CF_ERRNO("epoll_ctl: Add failed");
+ }
+ watched_.insert(fd);
+ return {};
+}
+
+Result<void> Epoll::AddOrModify(SharedFD fd, uint32_t events) {
+ std::unique_lock watched_lock(watched_mutex_, std::defer_lock);
+ std::shared_lock epoll_lock(epoll_mutex_, std::defer_lock);
+ std::lock(watched_lock, epoll_lock);
+ CF_EXPECT(epoll_fd_->IsOpen(), "Empty Epoll instance");
+
+ epoll_event event;
+ event.events = events;
+ event.data.fd = fd->fd_;
+ int operation = watched_.count(fd) == 0 ? EPOLL_CTL_ADD : EPOLL_CTL_MOD;
+ int success = epoll_ctl(epoll_fd_->fd_, operation, fd->fd_, &event);
+ if (success != 0) {
+ std::string operation_str = operation == EPOLL_CTL_ADD ? "add" : "modify";
+ return CF_ERRNO("epoll_ctl: Operation " << operation_str << " failed");
+ }
+ watched_.insert(fd);
+ return {};
+}
+
+Result<void> Epoll::Modify(SharedFD fd, uint32_t events) {
+ std::unique_lock watched_lock(watched_mutex_, std::defer_lock);
+ std::shared_lock epoll_lock(epoll_mutex_, std::defer_lock);
+ std::lock(watched_lock, epoll_lock);
+ CF_EXPECT(epoll_fd_->IsOpen(), "Empty Epoll instance");
+
+ if (watched_.count(fd) == 0) {
+ return CF_ERR("Watched set did not contain fd");
+ }
+ epoll_event event;
+ event.events = events;
+ event.data.fd = fd->fd_;
+ int success = epoll_ctl(epoll_fd_->fd_, EPOLL_CTL_MOD, fd->fd_, &event);
+ if (success != 0) {
+ return CF_ERRNO("epoll_ctl: Modify failed");
+ }
+ return {};
+}
+
+Result<void> Epoll::Delete(SharedFD fd) {
+ std::unique_lock watched_lock(watched_mutex_, std::defer_lock);
+ std::shared_lock epoll_lock(epoll_mutex_, std::defer_lock);
+ std::lock(watched_lock, epoll_lock);
+ CF_EXPECT(epoll_fd_->IsOpen(), "Empty Epoll instance");
+
+ if (watched_.count(fd) == 0) {
+ return CF_ERR("Watched set did not contain fd");
+ }
+ int success = epoll_ctl(epoll_fd_->fd_, EPOLL_CTL_DEL, fd->fd_, nullptr);
+ if (success != 0) {
+ return CF_ERRNO("epoll_ctl: Delete failed");
+ }
+ watched_.erase(fd);
+ return {};
+}
+
+Result<std::optional<EpollEvent>> Epoll::Wait() {
+ epoll_event event;
+ int success;
+ {
+ std::shared_lock lock(epoll_mutex_);
+ CF_EXPECT(epoll_fd_->IsOpen(), "Empty Epoll instance");
+ success = epoll_wait(epoll_fd_->fd_, &event, 1, -1);
+ }
+ if (success == -1) {
+ return CF_ERRNO("epoll_wait failed");
+ } else if (success == 0) {
+ return {};
+ } else if (success != 1) {
+ return CF_ERR("epoll_wait returned an unexpected value");
+ }
+ EpollEvent ret;
+ ret.events = event.events;
+ std::shared_lock lock(watched_mutex_);
+ for (const auto& watched : watched_) {
+ if (watched->fd_ == event.data.fd) {
+ ret.fd = watched;
+ break;
+ }
+ }
+ if (!ret.fd->IsOpen()) {
+ // Couldn't find the matching SharedFD to the file descriptor. We probably
+ // lost the race to lock watched_mutex_ against a delete call. Treat this
+ // as a spurious wakeup.
+ return {};
+ }
+ return ret;
+}
+
+} // namespace cuttlefish
diff --git a/common/libs/fs/epoll.h b/common/libs/fs/epoll.h
new file mode 100644
index 0000000..21bc618
--- /dev/null
+++ b/common/libs/fs/epoll.h
@@ -0,0 +1,71 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <sys/epoll.h>
+
+#include <memory>
+#include <optional>
+#include <set>
+#include <shared_mutex>
+
+#include "common/libs/fs/shared_fd.h"
+#include "common/libs/utils/result.h"
+
+namespace cuttlefish {
+
+struct EpollEvent {
+ SharedFD fd;
+ uint32_t events;
+};
+
+class Epoll {
+ public:
+ static Result<Epoll> Create();
+ Epoll(); // Invalid instance
+ Epoll(Epoll&&);
+ Epoll& operator=(Epoll&&);
+
+ Result<void> Add(SharedFD fd, uint32_t events);
+ Result<void> Modify(SharedFD fd, uint32_t events);
+ Result<void> AddOrModify(SharedFD fd, uint32_t events);
+ Result<void> Delete(SharedFD fd);
+ Result<std::optional<EpollEvent>> Wait();
+
+ private:
+ Epoll(SharedFD);
+
+ /**
+ * This read-write mutex is read-locked to perform epoll operations, and
+ * write-locked to replace the file descriptor.
+ *
+ * A read-write mutex is used here to make it possible to update the watched
+ * set while the epoll resource is being waited on by another thread, while
+ * excluding the possibility of the move constructor or assignment constructor
+ * from stealing the file descriptor out from under waiting threads.
+ */
+ std::shared_mutex epoll_mutex_;
+ SharedFD epoll_fd_;
+ /**
+ * This read-write mutex is read-locked when interacting with it as a const
+ * std::set, and write-locked when interacting with it as a std::set.
+ */
+ std::shared_mutex watched_mutex_;
+ std::set<SharedFD> watched_;
+};
+
+} // namespace cuttlefish
diff --git a/common/libs/fs/shared_buf.cc b/common/libs/fs/shared_buf.cc
index 61ff51e..f4b24dd 100644
--- a/common/libs/fs/shared_buf.cc
+++ b/common/libs/fs/shared_buf.cc
@@ -33,32 +33,34 @@
ssize_t WriteAll(SharedFD fd, const char* buf, size_t size) {
size_t total_written = 0;
ssize_t written = 0;
- while ((written = fd->Write((void*)&(buf[total_written]), size - total_written)) > 0) {
- if (written < 0) {
- errno = fd->GetErrno();
- return written;
+ do {
+ written = fd->Write((void*)&(buf[total_written]), size - total_written);
+ if (written <= 0) {
+ if (written < 0) {
+ errno = fd->GetErrno();
+ return written;
+ }
+ return total_written;
}
total_written += written;
- if (total_written == size) {
- break;
- }
- }
+ } while (total_written < size);
return total_written;
}
ssize_t ReadExact(SharedFD fd, char* buf, size_t size) {
size_t total_read = 0;
ssize_t read = 0;
- while ((read = fd->Read((void*)&(buf[total_read]), size - total_read)) > 0) {
- if (read < 0) {
- errno = fd->GetErrno();
- return read;
+ do {
+ read = fd->Read((void*)&(buf[total_read]), size - total_read);
+ if (read <= 0) {
+ if (read < 0) {
+ errno = fd->GetErrno();
+ return read;
+ }
+ return total_read;
}
total_read += read;
- if (total_read == size) {
- break;
- }
- }
+ } while (total_read < size);
return total_read;
}
diff --git a/common/libs/fs/shared_buf.h b/common/libs/fs/shared_buf.h
index eb63174..f1a02c2 100644
--- a/common/libs/fs/shared_buf.h
+++ b/common/libs/fs/shared_buf.h
@@ -30,6 +30,7 @@
*
* If a read error is encountered, returns -1. buf will contain any data read
* up until that point and errno will be set.
+ *
*/
ssize_t ReadAll(SharedFD fd, std::string* buf);
@@ -40,6 +41,11 @@
*
* If a read error is encountered, returns -1. buf will contain any data read
* up until that point and errno will be set.
+ *
+ * If the size of buf is 0, read(fd, buf, 0) is effectively called, which means
+ * error(s) might be detected. If detected, the return value would be -1.
+ * If not detected, the return value will be 0.
+ *
*/
ssize_t ReadExact(SharedFD fd, std::string* buf);
@@ -50,6 +56,11 @@
*
* If a read error is encountered, returns -1. buf will contain any data read
* up until that point and errno will be set.
+ *
+ * If the size of buf is 0, read(fd, buf, 0) is effectively called, which means
+ * error(s) might be detected. If detected, the return value would be -1.
+ * If not detected, the return value will be 0.
+ *
*/
ssize_t ReadExact(SharedFD fd, std::vector<char>* buf);
@@ -60,6 +71,11 @@
*
* If a read error is encountered, returns -1. buf will contain any data read
* up until that point and errno will be set.
+ *
+ * When the size is 0, read(fd, buf, 0) is effectively called, which means
+ * error(s) might be detected. If detected, the return value would be -1.
+ * If not detected, the return value will be 0.
+ *
*/
ssize_t ReadExact(SharedFD fd, char* buf, size_t size);
@@ -83,6 +99,12 @@
*
* If a write error is encountered, returns -1. Some data may have already been
* written to fd at that point.
+ *
+ * If the size of buf is 0, WriteAll returns 0 with no error set unless
+ * the fd is a regular file. If fd is a regular file, write(fd, buf, 0) is
+ * effectively called. It may detect errors; if detected, errno is set and
+ * -1 is returned. If not detected, 0 is returned with errno unchanged.
+ *
*/
ssize_t WriteAll(SharedFD fd, const std::string& buf);
@@ -93,6 +115,12 @@
*
* If a write error is encountered, returns -1. Some data may have already been
* written to fd at that point.
+ *
+ * If the size of buf is 0, WriteAll returns 0 with no error set unless
+ * the fd is a regular file. If fd is a regular file, write(fd, buf, 0) is
+ * effectively called. It may detect errors; if detected, errno is set and
+ * -1 is returned. If not detected, 0 is returned with errno unchanged.
+ *
*/
ssize_t WriteAll(SharedFD fd, const std::vector<char>& buf);
@@ -103,6 +131,12 @@
*
* If a write error is encountered, returns -1. Some data may have already been
* written to fd at that point.
+ *
+ * If size is 0, WriteAll returns 0 with no error set unless
+ * the fd is a regular file. If fd is a regular file, write(fd, buf, 0) is
+ * effectively called. It may detect errors; if detected, errno is set and
+ * -1 is returned. If not detected, 0 is returned with errno unchanged.
+ *
*/
ssize_t WriteAll(SharedFD fd, const char* buf, size_t size);
@@ -113,6 +147,12 @@
*
* If a write error is encountered, returns -1. Some data may have already been
* written to fd at that point.
+ *
+ * If ever sizeof(T) is 0, WriteAll returns 0 with no error set unless
+ * the fd is a regular file. If fd is a regular file, write(fd, buf, 0) is
+ * effectively called. It may detect errors; if detected, errno is set and
+ * -1 is returned. If not detected, 0 is returned with errno unchanged.
+ *
*/
template<typename T>
ssize_t WriteAllBinary(SharedFD fd, const T* binary_data) {
diff --git a/common/libs/fs/shared_fd.cpp b/common/libs/fs/shared_fd.cpp
index b770a38..b89db85 100644
--- a/common/libs/fs/shared_fd.cpp
+++ b/common/libs/fs/shared_fd.cpp
@@ -15,19 +15,24 @@
*/
#include "common/libs/fs/shared_fd.h"
-#include <sys/types.h>
-#include <sys/stat.h>
-#include <sys/mman.h>
-#include <sys/syscall.h>
-#include <cstddef>
#include <errno.h>
#include <fcntl.h>
#include <netinet/in.h>
+#include <poll.h>
+#include <sys/file.h>
+#include <sys/mman.h>
+#include <sys/stat.h>
+#include <sys/syscall.h>
+#include <sys/types.h>
#include <unistd.h>
+#include <cstddef>
+
#include <algorithm>
#include <vector>
-#include "android-base/logging.h"
+#include <android-base/logging.h>
+
+#include "common/libs/fs/shared_buf.h"
#include "common/libs/fs/shared_select.h"
// #define ENABLE_GCE_SHARED_FD_LOGGING 1
@@ -83,24 +88,51 @@
#endif
}
+bool IsRegularFile(const int fd) {
+ struct stat info;
+ if (fstat(fd, &info) < 0) {
+ return false;
+ }
+ return S_ISREG(info.st_mode);
+}
+
+constexpr size_t kPreferredBufferSize = 8192;
+
} // namespace
bool FileInstance::CopyFrom(FileInstance& in, size_t length) {
- std::vector<char> buffer(8192);
+ std::vector<char> buffer(kPreferredBufferSize);
while (length > 0) {
ssize_t num_read = in.Read(buffer.data(), std::min(buffer.size(), length));
- length -= num_read;
if (num_read <= 0) {
return false;
}
- if (Write(buffer.data(), num_read) != num_read) {
+ length -= num_read;
+
+ ssize_t written = 0;
+ do {
+ auto res = Write(buffer.data(), num_read);
+ if (res <= 0) {
// The caller will have to log an appropriate message.
- return false;
- }
+ return false;
+ }
+ written += res;
+ } while(written < num_read);
}
return true;
}
+bool FileInstance::CopyAllFrom(FileInstance& in) {
+ // FileInstance may have been constructed with a non-zero errno_ value because
+ // the errno variable is not zeroed out before.
+ errno_ = 0;
+ in.errno_ = 0;
+ while (CopyFrom(in, kPreferredBufferSize)) {
+ }
+ // Only return false if there was an actual error.
+ return !GetErrno() && !in.GetErrno();
+}
+
void FileInstance::Close() {
std::stringstream message;
if (fd_ == -1) {
@@ -122,6 +154,16 @@
fd_ = -1;
}
+bool FileInstance::Chmod(mode_t mode) {
+ int original_error = errno;
+ int ret = fchmod(fd_, mode);
+ if (ret != 0) {
+ errno_ = errno;
+ }
+ errno = original_error;
+ return ret == 0;
+}
+
int FileInstance::ConnectWithTimeout(const struct sockaddr* addr,
socklen_t addrlen,
struct timeval* timeout) {
@@ -134,7 +176,25 @@
LOG(ERROR) << "Failed to set O_NONBLOCK: " << StrError();
return -1;
}
- Connect(addr, addrlen); // This will return immediately because of O_NONBLOCK
+
+ auto connect_res = Connect(
+ addr, addrlen); // This will return immediately because of O_NONBLOCK
+
+ if (connect_res == 0) { // Immediate success
+ if (Fcntl(F_SETFL, original_flags) == -1) {
+ LOG(ERROR) << "Failed to restore original flags: " << StrError();
+ return -1;
+ }
+ return 0;
+ }
+
+ if (GetErrno() != EAGAIN && GetErrno() != EINPROGRESS) {
+ LOG(DEBUG) << "Immediate connection failure: " << StrError();
+ if (Fcntl(F_SETFL, original_flags) == -1) {
+ LOG(ERROR) << "Failed to restore original flags: " << StrError();
+ }
+ return -1;
+ }
fd_set fdset;
FD_ZERO(&fdset);
@@ -221,6 +281,24 @@
return rval;
}
+int SharedFD::Poll(std::vector<PollSharedFd>& fds, int timeout) {
+ return Poll(fds.data(), fds.size(), timeout);
+}
+
+int SharedFD::Poll(PollSharedFd* fds, size_t num_fds, int timeout) {
+ std::vector<pollfd> native_pollfds(num_fds);
+ for (size_t i = 0; i < num_fds; i++) {
+ native_pollfds[i].fd = fds[i].fd->fd_;
+ native_pollfds[i].events = fds[i].events;
+ native_pollfds[i].revents = 0;
+ }
+ int ret = poll(native_pollfds.data(), native_pollfds.size(), timeout);
+ for (size_t i = 0; i < num_fds; i++) {
+ fds[i].revents = native_pollfds[i].revents;
+ }
+ return ret;
+}
+
static void MakeAddress(const char* name, bool abstract,
struct sockaddr_un* dest, socklen_t* len) {
memset(dest, 0, sizeof(*dest));
@@ -285,6 +363,20 @@
return std::shared_ptr<FileInstance>(new FileInstance(fd, error_num));
}
+SharedFD SharedFD::MemfdCreateWithData(const std::string& name, const std::string& data, unsigned int flags) {
+ auto memfd = MemfdCreate(name, flags);
+ if (WriteAll(memfd, data) != data.size()) {
+ return ErrorFD(errno);
+ }
+ if (memfd->LSeek(0, SEEK_SET) != 0) {
+ return ErrorFD(memfd->GetErrno());
+ }
+ if (!memfd->Chmod(0700)) {
+ return ErrorFD(memfd->GetErrno());
+ }
+ return memfd;
+}
+
bool SharedFD::SocketPair(int domain, int type, int protocol,
SharedFD* fd0, SharedFD* fd1) {
int fds[2];
@@ -310,6 +402,31 @@
return SharedFD::Open(path, O_CREAT|O_WRONLY|O_TRUNC, mode);
}
+int SharedFD::Fchdir(SharedFD shared_fd) {
+ if (!shared_fd.value_) {
+ return -1;
+ }
+ errno = 0;
+ int rval = TEMP_FAILURE_RETRY(fchdir(shared_fd->fd_));
+ shared_fd->errno_ = errno;
+ return rval;
+}
+
+SharedFD SharedFD::Fifo(const std::string& path, mode_t mode) {
+ struct stat st;
+ if (TEMP_FAILURE_RETRY(stat(path.c_str(), &st)) == 0) {
+ if (TEMP_FAILURE_RETRY(remove(path.c_str())) != 0) {
+ return ErrorFD(errno);
+ }
+ }
+
+ int fd = TEMP_FAILURE_RETRY(mkfifo(path.c_str(), mode));
+ if (fd == -1) {
+ return ErrorFD(errno);
+ }
+ return Open(path, mode);
+}
+
SharedFD SharedFD::Socket(int domain, int socket_type, int protocol) {
int fd = TEMP_FAILURE_RETRY(socket(domain, socket_type, protocol));
if (fd == -1) {
@@ -455,12 +572,14 @@
addr.svm_cid = cid;
auto casted_addr = reinterpret_cast<sockaddr*>(&addr);
if (vsock->Bind(casted_addr, sizeof(addr)) == -1) {
- LOG(ERROR) << "Bind failed (" << vsock->StrError() << ")";
+ LOG(ERROR) << "Port " << port << " Bind failed (" << vsock->StrError()
+ << ")";
return SharedFD::ErrorFD(vsock->GetErrno());
}
if (type == SOCK_STREAM || type == SOCK_SEQPACKET) {
if (vsock->Listen(4) < 0) {
- LOG(ERROR) << "Listen failed (" << vsock->StrError() << ")";
+ LOG(ERROR) << "Port" << port << " Listen failed (" << vsock->StrError()
+ << ")";
return SharedFD::ErrorFD(vsock->GetErrno());
}
}
@@ -511,4 +630,240 @@
}
}
+/* static */ std::shared_ptr<FileInstance> FileInstance::ClosedInstance() {
+ return std::shared_ptr<FileInstance>(new FileInstance(-1, EBADF));
+}
+
+int FileInstance::Bind(const struct sockaddr* addr, socklen_t addrlen) {
+ errno = 0;
+ int rval = bind(fd_, addr, addrlen);
+ errno_ = errno;
+ return rval;
+}
+
+int FileInstance::Connect(const struct sockaddr* addr, socklen_t addrlen) {
+ errno = 0;
+ int rval = connect(fd_, addr, addrlen);
+ errno_ = errno;
+ return rval;
+}
+
+int FileInstance::UNMANAGED_Dup() {
+ errno = 0;
+ int rval = TEMP_FAILURE_RETRY(dup(fd_));
+ errno_ = errno;
+ return rval;
+}
+
+int FileInstance::UNMANAGED_Dup2(int newfd) {
+ errno = 0;
+ int rval = TEMP_FAILURE_RETRY(dup2(fd_, newfd));
+ errno_ = errno;
+ return rval;
+}
+
+int FileInstance::Fcntl(int command, int value) {
+ errno = 0;
+ int rval = TEMP_FAILURE_RETRY(fcntl(fd_, command, value));
+ errno_ = errno;
+ return rval;
+}
+
+int FileInstance::Flock(int operation) {
+ errno = 0;
+ int rval = TEMP_FAILURE_RETRY(flock(fd_, operation));
+ errno_ = errno;
+ return rval;
+}
+
+int FileInstance::GetSockName(struct sockaddr* addr, socklen_t* addrlen) {
+ errno = 0;
+ int rval = TEMP_FAILURE_RETRY(getsockname(fd_, addr, addrlen));
+ if (rval == -1) {
+ errno_ = errno;
+ }
+ return rval;
+}
+
+unsigned int FileInstance::VsockServerPort() {
+ struct sockaddr_vm vm_socket;
+ socklen_t length = sizeof(vm_socket);
+ GetSockName(reinterpret_cast<struct sockaddr*>(&vm_socket), &length);
+ return vm_socket.svm_port;
+}
+
+int FileInstance::Ioctl(int request, void* val) {
+ errno = 0;
+ int rval = TEMP_FAILURE_RETRY(ioctl(fd_, request, val));
+ errno_ = errno;
+ return rval;
+}
+
+int FileInstance::LinkAtCwd(const std::string& path) {
+ std::string name = "/proc/self/fd/";
+ name += std::to_string(fd_);
+ errno = 0;
+ int rval =
+ linkat(-1, name.c_str(), AT_FDCWD, path.c_str(), AT_SYMLINK_FOLLOW);
+ errno_ = errno;
+ return rval;
+}
+
+int FileInstance::Listen(int backlog) {
+ errno = 0;
+ int rval = listen(fd_, backlog);
+ errno_ = errno;
+ return rval;
+}
+
+off_t FileInstance::LSeek(off_t offset, int whence) {
+ errno = 0;
+ off_t rval = TEMP_FAILURE_RETRY(lseek(fd_, offset, whence));
+ errno_ = errno;
+ return rval;
+}
+
+ssize_t FileInstance::Recv(void* buf, size_t len, int flags) {
+ errno = 0;
+ ssize_t rval = TEMP_FAILURE_RETRY(recv(fd_, buf, len, flags));
+ errno_ = errno;
+ return rval;
+}
+
+ssize_t FileInstance::RecvMsg(struct msghdr* msg, int flags) {
+ errno = 0;
+ ssize_t rval = TEMP_FAILURE_RETRY(recvmsg(fd_, msg, flags));
+ errno_ = errno;
+ return rval;
+}
+
+ssize_t FileInstance::Read(void* buf, size_t count) {
+ errno = 0;
+ ssize_t rval = TEMP_FAILURE_RETRY(read(fd_, buf, count));
+ errno_ = errno;
+ return rval;
+}
+
+int FileInstance::EventfdRead(eventfd_t* value) {
+ errno = 0;
+ auto rval = eventfd_read(fd_, value);
+ errno_ = errno;
+ return rval;
+}
+
+ssize_t FileInstance::Send(const void* buf, size_t len, int flags) {
+ errno = 0;
+ ssize_t rval = TEMP_FAILURE_RETRY(send(fd_, buf, len, flags));
+ errno_ = errno;
+ return rval;
+}
+
+ssize_t FileInstance::SendMsg(const struct msghdr* msg, int flags) {
+ errno = 0;
+ ssize_t rval = TEMP_FAILURE_RETRY(sendmsg(fd_, msg, flags));
+ errno_ = errno;
+ return rval;
+}
+
+int FileInstance::Shutdown(int how) {
+ errno = 0;
+ int rval = shutdown(fd_, how);
+ errno_ = errno;
+ return rval;
+}
+
+int FileInstance::SetSockOpt(int level, int optname, const void* optval,
+ socklen_t optlen) {
+ errno = 0;
+ int rval = setsockopt(fd_, level, optname, optval, optlen);
+ errno_ = errno;
+ return rval;
+}
+
+int FileInstance::GetSockOpt(int level, int optname, void* optval,
+ socklen_t* optlen) {
+ errno = 0;
+ int rval = getsockopt(fd_, level, optname, optval, optlen);
+ errno_ = errno;
+ return rval;
+}
+
+int FileInstance::SetTerminalRaw() {
+ errno = 0;
+ termios terminal_settings;
+ int rval = tcgetattr(fd_, &terminal_settings);
+ errno_ = errno;
+ if (rval < 0) {
+ return rval;
+ }
+ cfmakeraw(&terminal_settings);
+ rval = tcsetattr(fd_, TCSANOW, &terminal_settings);
+ errno_ = errno;
+ return rval;
+}
+
+std::string FileInstance::StrError() const {
+ errno = 0;
+ return std::string(strerror(errno_));
+}
+
+ScopedMMap FileInstance::MMap(void* addr, size_t length, int prot, int flags,
+ off_t offset) {
+ errno = 0;
+ auto ptr = mmap(addr, length, prot, flags, fd_, offset);
+ errno_ = errno;
+ return ScopedMMap(ptr, length);
+}
+
+ssize_t FileInstance::Truncate(off_t length) {
+ errno = 0;
+ ssize_t rval = TEMP_FAILURE_RETRY(ftruncate(fd_, length));
+ errno_ = errno;
+ return rval;
+}
+
+ssize_t FileInstance::Write(const void* buf, size_t count) {
+ if (count == 0 && !IsRegular()) {
+ return 0;
+ }
+ errno = 0;
+ ssize_t rval = TEMP_FAILURE_RETRY(write(fd_, buf, count));
+ errno_ = errno;
+ return rval;
+}
+
+int FileInstance::EventfdWrite(eventfd_t value) {
+ errno = 0;
+ int rval = eventfd_write(fd_, value);
+ errno_ = errno;
+ return rval;
+}
+
+bool FileInstance::IsATTY() {
+ errno = 0;
+ int rval = isatty(fd_);
+ errno_ = errno;
+ return rval;
+}
+
+FileInstance::FileInstance(int fd, int in_errno)
+ : fd_(fd), errno_(in_errno), is_regular_file_(IsRegularFile(fd_)) {
+ // Ensure every file descriptor managed by a FileInstance has the CLOEXEC
+ // flag
+ TEMP_FAILURE_RETRY(fcntl(fd, F_SETFD, FD_CLOEXEC));
+ std::stringstream identity;
+ identity << "fd=" << fd << " @" << this;
+ identity_ = identity.str();
+}
+
+FileInstance* FileInstance::Accept(struct sockaddr* addr,
+ socklen_t* addrlen) const {
+ int fd = TEMP_FAILURE_RETRY(accept(fd_, addr, addrlen));
+ if (fd == -1) {
+ return new FileInstance(fd, errno);
+ } else {
+ return new FileInstance(fd, 0);
+ }
+}
+
} // namespace cuttlefish
diff --git a/common/libs/fs/shared_fd.h b/common/libs/fs/shared_fd.h
index 114d199..d24d987 100644
--- a/common/libs/fs/shared_fd.h
+++ b/common/libs/fs/shared_fd.h
@@ -34,6 +34,7 @@
#include <memory>
#include <sstream>
+#include <vector>
#include <errno.h>
#include <fcntl.h>
@@ -66,6 +67,8 @@
*/
namespace cuttlefish {
+struct PollSharedFd;
+class Epoll;
class FileInstance;
/**
@@ -126,10 +129,15 @@
// Fcntl or Dup functions.
static SharedFD Open(const std::string& pathname, int flags, mode_t mode = 0);
static SharedFD Creat(const std::string& pathname, mode_t mode);
+ static int Fchdir(SharedFD);
+ static SharedFD Fifo(const std::string& pathname, mode_t mode);
static bool Pipe(SharedFD* fd0, SharedFD* fd1);
static SharedFD Event(int initval = 0, int flags = 0);
static SharedFD MemfdCreate(const std::string& name, unsigned int flags = 0);
+ static SharedFD MemfdCreateWithData(const std::string& name, const std::string& data, unsigned int flags = 0);
static SharedFD Mkstemp(std::string* path);
+ static int Poll(PollSharedFd* fds, size_t num_fds, int timeout);
+ static int Poll(std::vector<PollSharedFd>& fds, int timeout);
static bool SocketPair(int domain, int type, int protocol, SharedFD* fd0,
SharedFD* fd1);
static SharedFD Socket(int domain, int socket_type, int protocol);
@@ -227,91 +235,51 @@
class FileInstance {
// Give SharedFD access to the aliasing constructor.
friend class SharedFD;
+ friend class Epoll;
public:
virtual ~FileInstance() { Close(); }
// This can't be a singleton because our shared_ptr's aren't thread safe.
- static std::shared_ptr<FileInstance> ClosedInstance() {
- return std::shared_ptr<FileInstance>(new FileInstance(-1, EBADF));
- }
+ static std::shared_ptr<FileInstance> ClosedInstance();
- int Bind(const struct sockaddr* addr, socklen_t addrlen) {
- errno = 0;
- int rval = bind(fd_, addr, addrlen);
- errno_ = errno;
- return rval;
- }
-
- int Connect(const struct sockaddr* addr, socklen_t addrlen) {
- errno = 0;
- int rval = connect(fd_, addr, addrlen);
- errno_ = errno;
- return rval;
- }
-
+ int Bind(const struct sockaddr* addr, socklen_t addrlen);
+ int Connect(const struct sockaddr* addr, socklen_t addrlen);
int ConnectWithTimeout(const struct sockaddr* addr, socklen_t addrlen,
struct timeval* timeout);
-
void Close();
+ bool Chmod(mode_t mode);
+
// Returns true if the entire input was copied.
// Otherwise an error will be set either on this file or the input.
// The non-const reference is needed to avoid binding this to a particular
// reference type.
bool CopyFrom(FileInstance& in, size_t length);
+ // Same as CopyFrom, but reads from input until EOF is reached.
+ bool CopyAllFrom(FileInstance& in);
- int UNMANAGED_Dup() {
- errno = 0;
- int rval = TEMP_FAILURE_RETRY(dup(fd_));
- errno_ = errno;
- return rval;
- }
+ int UNMANAGED_Dup();
+ int UNMANAGED_Dup2(int newfd);
+ int Fchdir();
+ int Fcntl(int command, int value);
- int UNMANAGED_Dup2(int newfd) {
- errno = 0;
- int rval = TEMP_FAILURE_RETRY(dup2(fd_, newfd));
- errno_ = errno;
- return rval;
- }
-
- int Fcntl(int command, int value) {
- errno = 0;
- int rval = TEMP_FAILURE_RETRY(fcntl(fd_, command, value));
- errno_ = errno;
- return rval;
- }
+ int Flock(int operation);
int GetErrno() const { return errno_; }
+ int GetSockName(struct sockaddr* addr, socklen_t* addrlen);
- int GetSockName(struct sockaddr* addr, socklen_t* addrlen) {
- errno = 0;
- int rval = TEMP_FAILURE_RETRY(getsockname(fd_, addr, addrlen));
- if (rval == -1) {
- errno_ = errno;
- }
- return rval;
- }
+ unsigned int VsockServerPort();
- unsigned int VsockServerPort() {
- struct sockaddr_vm vm_socket;
- socklen_t length = sizeof(vm_socket);
- GetSockName(reinterpret_cast<struct sockaddr*>(&vm_socket), &length);
- return vm_socket.svm_port;
- }
-
- int Ioctl(int request, void* val = nullptr) {
- errno = 0;
- int rval = TEMP_FAILURE_RETRY(ioctl(fd_, request, val));
- errno_ = errno;
- return rval;
- }
-
+ int Ioctl(int request, void* val = nullptr);
bool IsOpen() const { return fd_ != -1; }
// in probably isn't modified, but the API spec doesn't have const.
bool IsSet(fd_set* in) const;
+ // whether this is a regular file or not
+ bool IsRegular() const { return is_regular_file_; }
+
/**
* Adds a hard link to a file descriptor, based on the current working
* directory of the process or to some absolute path.
@@ -321,73 +289,16 @@
* Using this on a file opened with O_TMPFILE can link it into the filesystem.
*/
// Used with O_TMPFILE files to attach them to the filesystem.
- int LinkAtCwd(const std::string& path) {
- std::string name = "/proc/self/fd/";
- name += std::to_string(fd_);
- errno = 0;
- int rval = linkat(
- -1, name.c_str(), AT_FDCWD, path.c_str(), AT_SYMLINK_FOLLOW);
- errno_ = errno;
- return rval;
- }
-
- int Listen(int backlog) {
- errno = 0;
- int rval = listen(fd_, backlog);
- errno_ = errno;
- return rval;
- }
-
+ int LinkAtCwd(const std::string& path);
+ int Listen(int backlog);
static void Log(const char* message);
-
- off_t LSeek(off_t offset, int whence) {
- errno = 0;
- off_t rval = TEMP_FAILURE_RETRY(lseek(fd_, offset, whence));
- errno_ = errno;
- return rval;
- }
-
- ssize_t Recv(void* buf, size_t len, int flags) {
- errno = 0;
- ssize_t rval = TEMP_FAILURE_RETRY(recv(fd_, buf, len, flags));
- errno_ = errno;
- return rval;
- }
-
- ssize_t RecvMsg(struct msghdr* msg, int flags) {
- errno = 0;
- ssize_t rval = TEMP_FAILURE_RETRY(recvmsg(fd_, msg, flags));
- errno_ = errno;
- return rval;
- }
-
- ssize_t Read(void* buf, size_t count) {
- errno = 0;
- ssize_t rval = TEMP_FAILURE_RETRY(read(fd_, buf, count));
- errno_ = errno;
- return rval;
- }
-
- int EventfdRead(eventfd_t* value) {
- errno = 0;
- auto rval = eventfd_read(fd_, value);
- errno_ = errno;
- return rval;
- }
-
- ssize_t Send(const void* buf, size_t len, int flags) {
- errno = 0;
- ssize_t rval = TEMP_FAILURE_RETRY(send(fd_, buf, len, flags));
- errno_ = errno;
- return rval;
- }
-
- ssize_t SendMsg(const struct msghdr* msg, int flags) {
- errno = 0;
- ssize_t rval = TEMP_FAILURE_RETRY(sendmsg(fd_, msg, flags));
- errno_ = errno;
- return rval;
- }
+ off_t LSeek(off_t offset, int whence);
+ ssize_t Recv(void* buf, size_t len, int flags);
+ ssize_t RecvMsg(struct msghdr* msg, int flags);
+ ssize_t Read(void* buf, size_t count);
+ int EventfdRead(eventfd_t* value);
+ ssize_t Send(const void* buf, size_t len, int flags);
+ ssize_t SendMsg(const struct msghdr* msg, int flags);
template <typename... Args>
ssize_t SendFileDescriptors(const void* buf, size_t len, Args&&... sent_fds) {
@@ -399,118 +310,40 @@
return ret;
}
- int Shutdown(int how) {
- errno = 0;
- int rval = shutdown(fd_, how);
- errno_ = errno;
- return rval;
- }
-
+ int Shutdown(int how);
void Set(fd_set* dest, int* max_index) const;
-
- int SetSockOpt(int level, int optname, const void* optval, socklen_t optlen) {
- errno = 0;
- int rval = setsockopt(fd_, level, optname, optval, optlen);
- errno_ = errno;
- return rval;
- }
-
- int GetSockOpt(int level, int optname, void* optval, socklen_t* optlen) {
- errno = 0;
- int rval = getsockopt(fd_, level, optname, optval, optlen);
- errno_ = errno;
- return rval;
- }
-
- int SetTerminalRaw() {
- errno = 0;
- termios terminal_settings;
- int rval = tcgetattr(fd_, &terminal_settings);
- errno_ = errno;
- if (rval < 0) {
- return rval;
- }
- cfmakeraw(&terminal_settings);
- rval = tcsetattr(fd_, TCSANOW, &terminal_settings);
- errno_ = errno;
- return rval;
- }
-
- const char* StrError() const {
- errno = 0;
- FileInstance* s = const_cast<FileInstance*>(this);
- char* out = strerror_r(errno_, s->strerror_buf_, sizeof(strerror_buf_));
-
- // From man page:
- // strerror_r() returns a pointer to a string containing the error message.
- // This may be either a pointer to a string that the function stores in
- // buf, or a pointer to some (immutable) static string (in which case buf
- // is unused).
- if (out != s->strerror_buf_) {
- strncpy(s->strerror_buf_, out, sizeof(strerror_buf_));
- }
- return strerror_buf_;
- }
-
- ScopedMMap MMap(void* addr, size_t length, int prot, int flags,
- off_t offset) {
- errno = 0;
- auto ptr = mmap(addr, length, prot, flags, fd_, offset);
- errno_ = errno;
- return ScopedMMap(ptr, length);
- }
-
- ssize_t Truncate(off_t length) {
- errno = 0;
- ssize_t rval = TEMP_FAILURE_RETRY(ftruncate(fd_, length));
- errno_ = errno;
- return rval;
- }
-
- ssize_t Write(const void* buf, size_t count) {
- errno = 0;
- ssize_t rval = TEMP_FAILURE_RETRY(write(fd_, buf, count));
- errno_ = errno;
- return rval;
- }
-
- int EventfdWrite(eventfd_t value) {
- errno = 0;
- int rval = eventfd_write(fd_, value);
- errno_ = errno;
- return rval;
- }
-
- bool IsATTY() {
- errno = 0;
- int rval = isatty(fd_);
- errno_ = errno;
- return rval;
- }
+ int SetSockOpt(int level, int optname, const void* optval, socklen_t optlen);
+ int GetSockOpt(int level, int optname, void* optval, socklen_t* optlen);
+ int SetTerminalRaw();
+ std::string StrError() const;
+ ScopedMMap MMap(void* addr, size_t length, int prot, int flags, off_t offset);
+ ssize_t Truncate(off_t length);
+ /*
+ * If the file is a regular file and the count is 0, Write() may detect
+ * error(s) by calling write(fd, buf, 0) declared in <unistd.h>. If detected,
+ * it will return -1. If not, 0 will be returned. For non-regular files such
+ * as socket or pipe, write(fd, buf, 0) is not specified. Write(), however,
+ * will do nothing and just return 0.
+ *
+ */
+ ssize_t Write(const void* buf, size_t count);
+ int EventfdWrite(eventfd_t value);
+ bool IsATTY();
private:
- FileInstance(int fd, int in_errno) : fd_(fd), errno_(in_errno) {
- // Ensure every file descriptor managed by a FileInstance has the CLOEXEC
- // flag
- TEMP_FAILURE_RETRY(fcntl(fd, F_SETFD, FD_CLOEXEC));
- std::stringstream identity;
- identity << "fd=" << fd << " @" << this;
- identity_ = identity.str();
- }
-
- FileInstance* Accept(struct sockaddr* addr, socklen_t* addrlen) const {
- int fd = TEMP_FAILURE_RETRY(accept(fd_, addr, addrlen));
- if (fd == -1) {
- return new FileInstance(fd, errno);
- } else {
- return new FileInstance(fd, 0);
- }
- }
+ FileInstance(int fd, int in_errno);
+ FileInstance* Accept(struct sockaddr* addr, socklen_t* addrlen) const;
int fd_;
int errno_;
std::string identity_;
- char strerror_buf_[160];
+ bool is_regular_file_;
+};
+
+struct PollSharedFd {
+ SharedFD fd;
+ short events;
+ short revents;
};
/* Methods that need both a fully defined SharedFD and a fully defined
diff --git a/common/libs/net/netlink_client.cpp b/common/libs/net/netlink_client.cpp
index 7b2404f..b245f7e 100644
--- a/common/libs/net/netlink_client.cpp
+++ b/common/libs/net/netlink_client.cpp
@@ -54,9 +54,14 @@
char buf[4096];
struct iovec iov = { buf, sizeof(buf) };
struct sockaddr_nl sa;
- struct msghdr msg = { &sa, sizeof(sa), &iov, 1, NULL, 0, 0 };
+ struct msghdr msg {};
struct nlmsghdr *nh;
+ msg.msg_name = &sa;
+ msg.msg_namelen = sizeof(sa);
+ msg.msg_iov = &iov;
+ msg.msg_iovlen = 1;
+
int result = netlink_fd_->RecvMsg(&msg, 0);
if (result < 0) {
LOG(ERROR) << "Netlink error: " << strerror(errno);
diff --git a/common/libs/security/Android.bp b/common/libs/security/Android.bp
index 4f22951..fe18a0f 100644
--- a/common/libs/security/Android.bp
+++ b/common/libs/security/Android.bp
@@ -21,6 +21,7 @@
name: "libcuttlefish_security",
defaults: ["hidl_defaults", "cuttlefish_host"],
srcs: [
+ "confui_sign.cpp",
"gatekeeper_channel.cpp",
"keymaster_channel.cpp",
],
diff --git a/common/libs/security/confui_sign.cpp b/common/libs/security/confui_sign.cpp
new file mode 100644
index 0000000..d643585
--- /dev/null
+++ b/common/libs/security/confui_sign.cpp
@@ -0,0 +1,111 @@
+/*
+ * Copyright 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "common/libs/security/confui_sign.h"
+
+#include <android-base/logging.h>
+
+#include "common/libs/fs/shared_buf.h"
+
+namespace cuttlefish {
+bool ConfUiSignerImpl::Send(SharedFD output,
+ const confui::SignMessageError error,
+ const std::vector<std::uint8_t>& payload) {
+#define SET_FLAG_AND_RETURN_SEND \
+ sign_status_ |= kIoError; \
+ return false
+
+ // this looks redudant but makes sure that the byte-length of
+ // the error is guaranteed to be the same when it is received
+ confui::SignRawMessage msg;
+ msg.error_ = error;
+ auto n_written_err = WriteAllBinary(output, &(msg.error_));
+ if (n_written_err != sizeof(msg.error_)) {
+ sign_status_ |= kIoError;
+ SET_FLAG_AND_RETURN_SEND;
+ }
+ decltype(msg.payload_.size()) payload_size = payload.size();
+ auto n_written_payload_size = WriteAllBinary(output, &payload_size);
+ if (n_written_payload_size != sizeof(payload_size)) {
+ SET_FLAG_AND_RETURN_SEND;
+ }
+ const char* buf = reinterpret_cast<const char*>(payload.data());
+ auto n_written_payload = WriteAll(output, buf, payload.size());
+
+ if (n_written_payload != payload.size()) {
+ SET_FLAG_AND_RETURN_SEND;
+ }
+ return true;
+}
+
+std::optional<confui::SignRawMessage> ConfUiSignerImpl::Receive(
+ SharedFD input) {
+ confui::SignRawMessage msg;
+
+ auto n_read = ReadExactBinary(input, &(msg.error_));
+ if (n_read != sizeof(msg.error_)) {
+ sign_status_ |= kIoError;
+ }
+ if (msg.error_ != confui::SignMessageError::kOk) {
+ sign_status_ |= kLogicError;
+ }
+ if (!IsOk()) {
+ return std::nullopt;
+ }
+
+ decltype(msg.payload_.size()) payload_size = 0;
+ n_read = ReadExactBinary(input, &payload_size);
+ if (n_read != sizeof(payload_size)) {
+ sign_status_ |= kIoError;
+ return std::nullopt;
+ }
+
+ std::vector<std::uint8_t> buffer(payload_size);
+ char* buf_data = reinterpret_cast<char*>(buffer.data());
+ n_read = ReadExact(input, buf_data, payload_size);
+ if (n_read != payload_size) {
+ sign_status_ |= kIoError;
+ return std::nullopt;
+ }
+ msg.payload_.swap(buffer);
+ return {msg};
+}
+
+std::optional<confui::SignRawMessage> ConfUiSignSender::Receive() {
+ return impl_.Receive(server_fd_);
+}
+
+bool ConfUiSignSender::Send(const SignMessageError error,
+ const std::vector<std::uint8_t>& encoded_hmac) {
+ if (!impl_.IsOk()) {
+ return false;
+ }
+ impl_.Send(server_fd_, error, encoded_hmac);
+ return impl_.IsOk();
+}
+
+bool ConfUiSignRequester::Request(const std::vector<std::uint8_t>& message) {
+ impl_.Send(client_fd_, confui::SignMessageError::kOk, message);
+ return impl_.IsOk();
+}
+
+std::optional<confui::SignRawMessage> ConfUiSignRequester::Receive() {
+ if (!impl_.IsOk()) {
+ return std::nullopt;
+ }
+ return impl_.Receive(client_fd_);
+}
+} // namespace cuttlefish
diff --git a/common/libs/security/confui_sign.h b/common/libs/security/confui_sign.h
new file mode 100644
index 0000000..d85f936
--- /dev/null
+++ b/common/libs/security/confui_sign.h
@@ -0,0 +1,106 @@
+/*
+ * Copyright 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <optional>
+#include <vector>
+
+#include "common/libs/fs/shared_fd.h"
+
+namespace cuttlefish {
+namespace confui {
+enum class SignMessageError : std::uint8_t {
+ kOk = 0,
+ kUnknownError = 1,
+};
+
+struct SignRawMessage {
+ SignMessageError error_;
+ std::vector<std::uint8_t> payload_;
+};
+} // end of namespace confui
+
+class ConfUiSignerImpl {
+ // status and its mask bits
+ using ReceiveError = std::uint8_t;
+ static const ReceiveError kIoError = 1;
+ static const ReceiveError kLogicError = 2;
+
+ public:
+ ConfUiSignerImpl() : sign_status_(0) {}
+
+ bool IsIoError() const { return (kIoError & sign_status_) != 0; }
+
+ bool IsLogicError() const { return (kLogicError & sign_status_) != 0; }
+
+ bool IsOk() const { return !IsIoError() && !IsLogicError(); }
+
+ // set the sign_status_ if there was an error
+ // TODO(kwstephenkim@): use android::base::Result. aosp/1940753
+ bool Send(SharedFD output, const confui::SignMessageError error,
+ const std::vector<std::uint8_t>& payload);
+ std::optional<confui::SignRawMessage> Receive(SharedFD input);
+
+ private:
+ ReceiveError sign_status_;
+};
+
+/*
+ * secure_env will use this in order:
+ *
+ * Receive() // receive request
+ * Send() // send signature
+ */
+class ConfUiSignSender {
+ using SignMessageError = confui::SignMessageError;
+
+ public:
+ ConfUiSignSender(SharedFD fd) : server_fd_(fd) {}
+
+ // note that the error is IO error
+ std::optional<confui::SignRawMessage> Receive();
+ bool Send(const SignMessageError error,
+ const std::vector<std::uint8_t>& encoded_hmac);
+
+ bool IsOk() const { return impl_.IsOk(); }
+ bool IsIoError() const { return impl_.IsIoError(); }
+ bool IsLogicError() const { return impl_.IsLogicError(); }
+
+ private:
+ SharedFD server_fd_;
+ ConfUiSignerImpl impl_;
+};
+
+/**
+ * confirmation UI host will use this in this order:
+ *
+ * Request()
+ * Receive()
+ */
+class ConfUiSignRequester {
+ using SignMessageError = confui::SignMessageError;
+
+ public:
+ ConfUiSignRequester(SharedFD fd) : client_fd_(fd) {}
+ bool Request(const std::vector<std::uint8_t>& message);
+ std::optional<confui::SignRawMessage> Receive();
+
+ private:
+ SharedFD client_fd_;
+ ConfUiSignerImpl impl_;
+};
+} // end of namespace cuttlefish
diff --git a/common/libs/security/keymaster_channel.cpp b/common/libs/security/keymaster_channel.cpp
index 7b3ab86..fde5aa5 100644
--- a/common/libs/security/keymaster_channel.cpp
+++ b/common/libs/security/keymaster_channel.cpp
@@ -14,10 +14,17 @@
* limitations under the License.
*/
-#include "keymaster_channel.h"
+#include "common/libs/security/keymaster_channel.h"
+
+#include <cstdlib>
+#include <memory>
+#include <ostream>
+#include <string>
#include <android-base/logging.h>
-#include "keymaster/android_keymaster_utils.h"
+#include <keymaster/android_keymaster_messages.h>
+#include <keymaster/mem.h>
+#include <keymaster/serializable.h>
#include "common/libs/fs/shared_buf.h"
@@ -25,7 +32,7 @@
ManagedKeymasterMessage CreateKeymasterMessage(
AndroidKeymasterCommand command, bool is_response, size_t payload_size) {
- auto memory = new uint8_t[payload_size + sizeof(keymaster_message)];
+ auto memory = std::malloc(payload_size + sizeof(keymaster_message));
auto message = reinterpret_cast<keymaster_message*>(memory);
message->cmd = command;
message->is_response = is_response;
@@ -37,7 +44,7 @@
{
keymaster::Eraser(ptr, sizeof(keymaster_message) + ptr->payload_size);
}
- delete reinterpret_cast<uint8_t*>(ptr);
+ std::free(ptr);
}
KeymasterChannel::KeymasterChannel(SharedFD input, SharedFD output)
diff --git a/common/libs/security/keymaster_channel.h b/common/libs/security/keymaster_channel.h
index 49c3843..eec5a7f 100644
--- a/common/libs/security/keymaster_channel.h
+++ b/common/libs/security/keymaster_channel.h
@@ -16,13 +16,15 @@
#pragma once
-#include "keymaster/android_keymaster_messages.h"
-#include "keymaster/serializable.h"
+#include <cstddef>
+#include <cstdint>
+#include <memory>
+
+#include <keymaster/android_keymaster_messages.h>
+#include <keymaster/serializable.h>
#include "common/libs/fs/shared_fd.h"
-#include <memory>
-
namespace keymaster {
/**
@@ -33,8 +35,8 @@
struct keymaster_message {
AndroidKeymasterCommand cmd : 31;
bool is_response : 1;
- uint32_t payload_size;
- uint8_t payload[0];
+ std::uint32_t payload_size;
+ std::uint8_t payload[0];
};
} // namespace keymaster
@@ -61,8 +63,9 @@
* Allocates memory for a keymaster_message carrying a message of size
* `payload_size`.
*/
-ManagedKeymasterMessage CreateKeymasterMessage(
- AndroidKeymasterCommand command, bool is_response, size_t payload_size);
+ManagedKeymasterMessage CreateKeymasterMessage(AndroidKeymasterCommand command,
+ bool is_response,
+ std::size_t payload_size);
/*
* Interface for communication channels that synchronously communicate Keymaster
diff --git a/common/libs/time/monotonic_time.h b/common/libs/time/monotonic_time.h
deleted file mode 100644
index 2839001..0000000
--- a/common/libs/time/monotonic_time.h
+++ /dev/null
@@ -1,281 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-#pragma once
-
-#include <stdint.h>
-#include <time.h>
-
-namespace cuttlefish {
-namespace time {
-
-static const int64_t kNanosecondsPerSecond = 1000000000;
-
-class TimeDifference {
- public:
- TimeDifference(time_t seconds, long nanoseconds, int64_t scale) :
- scale_(scale), truncated_(false) {
- ts_.tv_sec = seconds;
- ts_.tv_nsec = nanoseconds;
- if (scale_ == kNanosecondsPerSecond) {
- truncated_ = true;
- truncated_ns_ = 0;
- }
- }
-
- TimeDifference(const TimeDifference& in, int64_t scale) :
- scale_(scale), truncated_(false) {
- ts_ = in.GetTS();
- if (scale_ == kNanosecondsPerSecond) {
- truncated_ = true;
- truncated_ns_ = 0;
- } else if ((in.scale_ % scale_) == 0) {
- truncated_ = true;
- truncated_ns_ = ts_.tv_nsec;
- }
- }
-
- TimeDifference(const struct timespec& in, int64_t scale) :
- ts_(in), scale_(scale), truncated_(false) { }
-
- TimeDifference operator*(const uint32_t factor) {
- TimeDifference rval = *this;
- rval.ts_.tv_sec = ts_.tv_sec * factor;
- // Create temporary variable to hold the multiplied
- // nanoseconds so that no overflow is possible.
- // Nanoseconds must be in [0, 10^9) and so all are less
- // then 2^30. Even multiplied by the largest uint32
- // this will fit in a 64-bit int without overflow.
- int64_t tv_nsec = static_cast<int64_t>(ts_.tv_nsec) * factor;
- rval.ts_.tv_sec += (tv_nsec / kNanosecondsPerSecond);
- rval.ts_.tv_nsec = tv_nsec % kNanosecondsPerSecond;
- return rval;
- }
-
- TimeDifference operator+(const TimeDifference& other) const {
- struct timespec ret = ts_;
- ret.tv_nsec = (ts_.tv_nsec + other.ts_.tv_nsec) % 1000000000;
- ret.tv_sec = (ts_.tv_sec + other.ts_.tv_sec) +
- (ts_.tv_nsec + other.ts_.tv_nsec) / 1000000000;
- return TimeDifference(ret, scale_ < other.scale_ ? scale_: other.scale_);
- }
-
- TimeDifference operator-(const TimeDifference& other) const {
- struct timespec ret = ts_;
- // Keeps nanoseconds positive and allow negative numbers only on
- // seconds.
- ret.tv_nsec = (1000000000 + ts_.tv_nsec - other.ts_.tv_nsec) % 1000000000;
- ret.tv_sec = (ts_.tv_sec - other.ts_.tv_sec) -
- (ts_.tv_nsec < other.ts_.tv_nsec ? 1 : 0);
- return TimeDifference(ret, scale_ < other.scale_ ? scale_: other.scale_);
- }
-
- bool operator<(const TimeDifference& other) const {
- return ts_.tv_sec < other.ts_.tv_sec ||
- (ts_.tv_sec == other.ts_.tv_sec && ts_.tv_nsec < other.ts_.tv_nsec);
- }
-
- int64_t count() const {
- return ts_.tv_sec * (kNanosecondsPerSecond / scale_) + ts_.tv_nsec / scale_;
- }
-
- time_t seconds() const {
- return ts_.tv_sec;
- }
-
- long subseconds_in_ns() const {
- if (!truncated_) {
- truncated_ns_ = (ts_.tv_nsec / scale_) * scale_;
- truncated_ = true;
- }
- return truncated_ns_;
- }
-
- struct timespec GetTS() const {
- // We can't assume C++11, so avoid extended initializer lists.
- struct timespec rval = { ts_.tv_sec, subseconds_in_ns()};
- return rval;
- }
-
- protected:
- struct timespec ts_;
- int64_t scale_;
- mutable bool truncated_;
- mutable long truncated_ns_;
-};
-
-class MonotonicTimePoint {
- public:
- static MonotonicTimePoint Now() {
- struct timespec ts;
-#ifdef CLOCK_MONOTONIC_RAW
- // WARNING:
- // While we do have CLOCK_MONOTONIC_RAW, we can't depend on it until:
- // - ALL places relying on MonotonicTimePoint are fixed,
- // - pthread supports pthread_timewait_monotonic.
- //
- // This is currently observable as a LEGITIMATE problem while running
- // pthread_test. DO NOT revert this to CLOCK_MONOTONIC_RAW until test
- // passes.
- clock_gettime(CLOCK_MONOTONIC, &ts);
-#else
- clock_gettime(CLOCK_MONOTONIC, &ts);
-#endif
- return MonotonicTimePoint(ts);
- }
-
- MonotonicTimePoint() {
- ts_.tv_sec = 0;
- ts_.tv_nsec = 0;
- }
-
- explicit MonotonicTimePoint(const struct timespec& ts) {
- ts_ = ts;
- }
-
- TimeDifference SinceEpoch() const {
- return TimeDifference(ts_, 1);
- }
-
- TimeDifference operator-(const MonotonicTimePoint& other) const {
- struct timespec rval;
- rval.tv_sec = ts_.tv_sec - other.ts_.tv_sec;
- rval.tv_nsec = ts_.tv_nsec - other.ts_.tv_nsec;
- if (rval.tv_nsec < 0) {
- --rval.tv_sec;
- rval.tv_nsec += kNanosecondsPerSecond;
- }
- return TimeDifference(rval, 1);
- }
-
- MonotonicTimePoint operator+(const TimeDifference& other) const {
- MonotonicTimePoint rval = *this;
- rval.ts_.tv_sec += other.seconds();
- rval.ts_.tv_nsec += other.subseconds_in_ns();
- if (rval.ts_.tv_nsec >= kNanosecondsPerSecond) {
- ++rval.ts_.tv_sec;
- rval.ts_.tv_nsec -= kNanosecondsPerSecond;
- }
- return rval;
- }
-
- bool operator==(const MonotonicTimePoint& other) const {
- return (ts_.tv_sec == other.ts_.tv_sec) &&
- (ts_.tv_nsec == other.ts_.tv_nsec);
- }
-
- bool operator!=(const MonotonicTimePoint& other) const {
- return !(*this == other);
- }
-
- bool operator<(const MonotonicTimePoint& other) const {
- return ((ts_.tv_sec - other.ts_.tv_sec) < 0) ||
- ((ts_.tv_sec == other.ts_.tv_sec) &&
- (ts_.tv_nsec < other.ts_.tv_nsec));
- }
-
- bool operator>(const MonotonicTimePoint& other) const {
- return other < *this;
- }
-
- bool operator<=(const MonotonicTimePoint& other) const {
- return !(*this > other);
- }
-
- bool operator>=(const MonotonicTimePoint& other) const {
- return !(*this < other);
- }
-
- MonotonicTimePoint& operator+=(const TimeDifference& other) {
- ts_.tv_sec += other.seconds();
- ts_.tv_nsec += other.subseconds_in_ns();
- if (ts_.tv_nsec >= kNanosecondsPerSecond) {
- ++ts_.tv_sec;
- ts_.tv_nsec -= kNanosecondsPerSecond;
- }
- return *this;
- }
-
- MonotonicTimePoint& operator-=(const TimeDifference& other) {
- ts_.tv_sec -= other.seconds();
- ts_.tv_nsec -= other.subseconds_in_ns();
- if (ts_.tv_nsec < 0) {
- --ts_.tv_sec;
- ts_.tv_nsec += kNanosecondsPerSecond;
- }
- return *this;
- }
-
- void ToTimespec(struct timespec* dest) const {
- *dest = ts_;
- }
-
- protected:
- struct timespec ts_;
-};
-
-class Seconds : public TimeDifference {
- public:
- explicit Seconds(const TimeDifference& difference) :
- TimeDifference(difference, kNanosecondsPerSecond) { }
-
- Seconds(int64_t seconds) :
- TimeDifference(seconds, 0, kNanosecondsPerSecond) { }
-};
-
-class Milliseconds : public TimeDifference {
- public:
- explicit Milliseconds(const TimeDifference& difference) :
- TimeDifference(difference, kScale) { }
-
- Milliseconds(int64_t ms) : TimeDifference(
- ms / 1000, (ms % 1000) * kScale, kScale) { }
-
- protected:
- static const int kScale = kNanosecondsPerSecond / 1000;
-};
-
-class Microseconds : public TimeDifference {
- public:
- explicit Microseconds(const TimeDifference& difference) :
- TimeDifference(difference, kScale) { }
-
- Microseconds(int64_t micros) : TimeDifference(
- micros / 1000000, (micros % 1000000) * kScale, kScale) { }
-
- protected:
- static const int kScale = kNanosecondsPerSecond / 1000000;
-};
-
-class Nanoseconds : public TimeDifference {
- public:
- explicit Nanoseconds(const TimeDifference& difference) :
- TimeDifference(difference, 1) { }
- Nanoseconds(int64_t ns) : TimeDifference(ns / kNanosecondsPerSecond,
- ns % kNanosecondsPerSecond, 1) { }
-};
-
-} // namespace time
-} // namespace cuttlefish
-
-/**
- * Legacy support for microseconds. Use MonotonicTimePoint in new code.
- */
-static const int64_t kSecsToUsecs = static_cast<int64_t>(1000) * 1000;
-
-static inline int64_t get_monotonic_usecs() {
- return cuttlefish::time::Microseconds(
- cuttlefish::time::MonotonicTimePoint::Now().SinceEpoch()).count();
-}
diff --git a/common/libs/utils/Android.bp b/common/libs/utils/Android.bp
index 860b684..412bbef 100644
--- a/common/libs/utils/Android.bp
+++ b/common/libs/utils/Android.bp
@@ -23,14 +23,17 @@
"archive.cpp",
"subprocess.cpp",
"environment.cpp",
- "size_utils.cpp",
+ "flag_parser.cpp",
+ "shared_fd_flag.cpp",
"files.cpp",
"users.cpp",
"network.cpp",
"base64.cpp",
"tcp_socket.cpp",
"tee_logging.cpp",
+ "unix_sockets.cpp",
"vsock_connection.cpp",
+ "socket2socket_proxy.cpp",
],
shared: {
shared_libs: [
@@ -53,6 +56,28 @@
defaults: ["cuttlefish_host"],
}
+cc_test_host {
+ name: "libcuttlefish_utils_test",
+ srcs: [
+ "flag_parser_test.cpp",
+ "unix_sockets_test.cpp",
+ ],
+ static_libs: [
+ "libbase",
+ "libcuttlefish_fs",
+ "libcuttlefish_utils",
+ ],
+ shared_libs: [
+ "libcrypto",
+ "liblog",
+ "libxml2",
+ ],
+ test_options: {
+ unit_test: true,
+ },
+ defaults: ["cuttlefish_host"],
+}
+
cc_library {
name: "libvsock_utils",
srcs: ["vsock_connection.cpp"],
diff --git a/common/libs/utils/archive.cpp b/common/libs/utils/archive.cpp
index 05fbf5f..0610327 100644
--- a/common/libs/utils/archive.cpp
+++ b/common/libs/utils/archive.cpp
@@ -16,7 +16,9 @@
#include "common/libs/utils/archive.h"
+#include <ostream>
#include <string>
+#include <utility>
#include <vector>
#include <android-base/strings.h>
@@ -79,15 +81,15 @@
bsdtar_cmd.AddParameter(file);
bsdtar_cmd.AddParameter("-O");
bsdtar_cmd.AddParameter(path);
- std::string stdout, stderr;
- auto ret = RunWithManagedStdio(std::move(bsdtar_cmd), nullptr, &stdout,
- nullptr);
+ std::string stdout_str;
+ auto ret =
+ RunWithManagedStdio(std::move(bsdtar_cmd), nullptr, &stdout_str, nullptr);
if (ret != 0) {
LOG(ERROR) << "Could not extract \"" << path << "\" from \"" << file
<< "\" to memory.";
return "";
}
- return stdout;
+ return stdout_str;
}
} // namespace cuttlefish
diff --git a/common/libs/utils/base64.cpp b/common/libs/utils/base64.cpp
index 1aa09ce..837c486 100644
--- a/common/libs/utils/base64.cpp
+++ b/common/libs/utils/base64.cpp
@@ -16,19 +16,25 @@
#include "common/libs/utils/base64.h"
+#include <cstddef>
+#include <cstdint>
+#include <string>
+#include <vector>
+
#include <openssl/base64.h>
namespace cuttlefish {
-bool EncodeBase64(const void *data, size_t size, std::string *out) {
- size_t enc_len = 0;
+bool EncodeBase64(const void *data, std::size_t size, std::string *out) {
+ std::size_t enc_len = 0;
auto len_res = EVP_EncodedLength(&enc_len, size);
if (!len_res) {
return false;
}
out->resize(enc_len);
- auto enc_res = EVP_EncodeBlock(reinterpret_cast<uint8_t *>(out->data()),
- reinterpret_cast<const uint8_t *>(data), size);
+ auto enc_res =
+ EVP_EncodeBlock(reinterpret_cast<std::uint8_t *>(out->data()),
+ reinterpret_cast<const std::uint8_t *>(data), size);
if (enc_res < 0) {
return false;
}
@@ -36,15 +42,15 @@
return true;
}
-bool DecodeBase64(const std::string &data, std::vector<uint8_t> *buffer) {
- size_t out_len;
+bool DecodeBase64(const std::string &data, std::vector<std::uint8_t> *buffer) {
+ std::size_t out_len;
auto len_res = EVP_DecodedLength(&out_len, data.size());
if (!len_res) {
return false;
}
buffer->resize(out_len);
return EVP_DecodeBase64(buffer->data(), &out_len, out_len,
- reinterpret_cast<const uint8_t *>(data.data()),
+ reinterpret_cast<const std::uint8_t *>(data.data()),
data.size());
}
diff --git a/common/libs/utils/base64.h b/common/libs/utils/base64.h
index dd262c3..c390232 100644
--- a/common/libs/utils/base64.h
+++ b/common/libs/utils/base64.h
@@ -16,14 +16,15 @@
#pragma once
-#include <cinttypes>
+#include <cstddef>
+#include <cstdint>
#include <string>
#include <vector>
namespace cuttlefish {
-bool EncodeBase64(const void* _data, size_t size, std::string* out);
+bool EncodeBase64(const void* _data, std::size_t size, std::string* out);
-bool DecodeBase64(const std::string& data, std::vector<uint8_t>* buffer);
+bool DecodeBase64(const std::string& data, std::vector<std::uint8_t>* buffer);
} // namespace cuttlefish
diff --git a/common/libs/utils/environment.cpp b/common/libs/utils/environment.cpp
index ec27290..b2643fa 100644
--- a/common/libs/utils/environment.cpp
+++ b/common/libs/utils/environment.cpp
@@ -15,14 +15,17 @@
*/
#include "common/libs/utils/environment.h"
-#include "common/libs/utils/files.h"
-#include <stdlib.h>
-#include <stdio.h>
-#include <iostream>
+#include <cstdio>
+#include <cstdlib>
+#include <memory>
+#include <ostream>
+#include <string>
#include <android-base/logging.h>
+#include "common/libs/utils/files.h"
+
namespace cuttlefish {
std::string StringFromEnv(const std::string& varname,
diff --git a/common/libs/utils/files.cpp b/common/libs/utils/files.cpp
index ea84b32..c1b1778 100644
--- a/common/libs/utils/files.cpp
+++ b/common/libs/utils/files.cpp
@@ -18,25 +18,40 @@
#include <android-base/logging.h>
+#include <dirent.h>
+#include <fcntl.h>
+#include <ftw.h>
+#include <libgen.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <unistd.h>
+
#include <array>
+#include <cerrno>
+#include <chrono>
#include <climits>
#include <cstdio>
#include <cstdlib>
+#include <cstring>
#include <fstream>
-#include <libgen.h>
-#include <sys/types.h>
-#include <sys/stat.h>
-#include <unistd.h>
-#include <dirent.h>
+#include <ios>
+#include <iosfwd>
+#include <istream>
+#include <memory>
+#include <ostream>
+#include <ratio>
+#include <string>
#include <vector>
+#include <android-base/macros.h>
+
#include "common/libs/fs/shared_fd.h"
namespace cuttlefish {
-bool FileExists(const std::string& path) {
+bool FileExists(const std::string& path, bool follow_symlinks) {
struct stat st;
- return stat(path.c_str(), &st) == 0;
+ return (follow_symlinks ? stat : lstat)(path.c_str(), &st) == 0;
}
bool FileHasContent(const std::string& path) {
@@ -56,9 +71,9 @@
return ret;
}
-bool DirectoryExists(const std::string& path) {
+bool DirectoryExists(const std::string& path, bool follow_symlinks) {
struct stat st;
- if (stat(path.c_str(), &st) == -1) {
+ if ((follow_symlinks ? stat : lstat)(path.c_str(), &st) == -1) {
return false;
}
if ((st.st_mode & S_IFMT) != S_IFDIR) {
@@ -67,6 +82,18 @@
return true;
}
+Result<void> EnsureDirectoryExists(const std::string& directory_path) {
+ if (!DirectoryExists(directory_path)) {
+ LOG(DEBUG) << "Setting up " << directory_path;
+ if (mkdir(directory_path.c_str(), S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH) <
+ 0 &&
+ errno != EEXIST) {
+ return CF_ERRNO("Failed to create dir: \"" << directory_path);
+ }
+ }
+ return {};
+}
+
bool IsDirectoryEmpty(const std::string& path) {
auto direc = ::opendir(path.c_str());
if (!direc) {
@@ -88,6 +115,40 @@
return true;
}
+bool RecursivelyRemoveDirectory(const std::string& path) {
+ // Copied from libbase TemporaryDir destructor.
+ auto callback = [](const char* child, const struct stat*, int file_type,
+ struct FTW*) -> int {
+ switch (file_type) {
+ case FTW_D:
+ case FTW_DP:
+ case FTW_DNR:
+ if (rmdir(child) == -1) {
+ PLOG(ERROR) << "rmdir " << child;
+ }
+ break;
+ case FTW_NS:
+ default:
+ if (rmdir(child) != -1) {
+ break;
+ }
+ // FALLTHRU (for gcc, lint, pcc, etc; and following for clang)
+ FALLTHROUGH_INTENDED;
+ case FTW_F:
+ case FTW_SL:
+ case FTW_SLN:
+ if (unlink(child) == -1) {
+ PLOG(ERROR) << "unlink " << child;
+ }
+ break;
+ }
+ return 0;
+ };
+
+ return nftw(path.c_str(), callback, 128, FTW_DEPTH | FTW_MOUNT | FTW_PHYS) ==
+ 0;
+}
+
std::string AbsolutePath(const std::string& path) {
if (path.empty()) {
return {};
@@ -117,6 +178,11 @@
return st.st_size;
}
+bool MakeFileExecutable(const std::string& path) {
+ LOG(DEBUG) << "Making " << path << " executable";
+ return chmod(path.c_str(), S_IRWXU) == 0;
+}
+
// TODO(schuffelen): Use std::filesystem::last_write_time when on C++17
std::chrono::system_clock::time_point FileModificationTime(const std::string& path) {
struct stat st;
@@ -138,15 +204,18 @@
}
bool RemoveFile(const std::string& file) {
- LOG(DEBUG) << "Removing " << file;
+ LOG(DEBUG) << "Removing file " << file;
return remove(file.c_str()) == 0;
}
-
std::string ReadFile(const std::string& file) {
std::string contents;
std::ifstream in(file, std::ios::in | std::ios::binary);
in.seekg(0, std::ios::end);
+ if (in.fail()) {
+ // TODO(schuffelen): Return a failing Result instead
+ return "";
+ }
contents.resize(in.tellg());
in.seekg(0, std::ios::beg);
in.read(&contents[0], contents.size());
@@ -156,6 +225,10 @@
std::string CurrentDirectory() {
char* path = getcwd(nullptr, 0);
+ if (path == nullptr) {
+ PLOG(ERROR) << "`getcwd(nullptr, 0)` failed";
+ return "";
+ }
std::string ret(path);
free(path);
return ret;
@@ -222,4 +295,10 @@
return ret;
}
+bool FileIsSocket(const std::string& path) {
+ struct stat st;
+ return stat(path.c_str(), &st) == 0 && S_ISSOCK(st.st_mode);
+}
+
+
} // namespace cuttlefish
diff --git a/common/libs/utils/files.h b/common/libs/utils/files.h
index ff1c8d3..fbe6b70 100644
--- a/common/libs/utils/files.h
+++ b/common/libs/utils/files.h
@@ -19,20 +19,28 @@
#include <chrono>
#include <string>
+#include <vector>
+
+#include "common/libs/utils/result.h"
namespace cuttlefish {
-bool FileExists(const std::string& path);
+bool FileExists(const std::string& path, bool follow_symlinks = true);
bool FileHasContent(const std::string& path);
std::vector<std::string> DirectoryContents(const std::string& path);
-bool DirectoryExists(const std::string& path);
+bool DirectoryExists(const std::string& path, bool follow_symlinks = true);
+Result<void> EnsureDirectoryExists(const std::string& directory_path);
bool IsDirectoryEmpty(const std::string& path);
+bool RecursivelyRemoveDirectory(const std::string& path);
off_t FileSize(const std::string& path);
bool RemoveFile(const std::string& file);
bool RenameFile(const std::string& old_name, const std::string& new_name);
std::string ReadFile(const std::string& file);
+bool MakeFileExecutable(const std::string& path);
std::chrono::system_clock::time_point FileModificationTime(const std::string& path);
std::string cpp_dirname(const std::string& str);
std::string cpp_basename(const std::string& str);
+// Whether a file exists and is a unix socket
+bool FileIsSocket(const std::string& path);
// The returned value may contain .. or . if these are present in the path
// argument.
diff --git a/common/libs/utils/flag_parser.cpp b/common/libs/utils/flag_parser.cpp
new file mode 100644
index 0000000..76aeced
--- /dev/null
+++ b/common/libs/utils/flag_parser.cpp
@@ -0,0 +1,485 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "common/libs/utils/flag_parser.h"
+
+#include <cerrno>
+#include <cstdint>
+#include <cstdlib>
+#include <cstring>
+#include <functional>
+#include <optional>
+#include <ostream>
+#include <string>
+#include <string_view>
+#include <type_traits>
+#include <unordered_map>
+#include <unordered_set>
+#include <utility>
+#include <vector>
+
+#include <android-base/logging.h>
+#include <android-base/strings.h>
+
+namespace cuttlefish {
+
+std::ostream& operator<<(std::ostream& out, const FlagAlias& alias) {
+ switch (alias.mode) {
+ case FlagAliasMode::kFlagExact:
+ return out << alias.name;
+ case FlagAliasMode::kFlagPrefix:
+ return out << alias.name << "*";
+ case FlagAliasMode::kFlagConsumesFollowing:
+ return out << alias.name << " *";
+ default:
+ LOG(FATAL) << "Unexpected flag alias mode " << (int)alias.mode;
+ }
+ return out;
+}
+
+Flag& Flag::UnvalidatedAlias(const FlagAlias& alias) & {
+ aliases_.push_back(alias);
+ return *this;
+}
+Flag Flag::UnvalidatedAlias(const FlagAlias& alias) && {
+ aliases_.push_back(alias);
+ return *this;
+}
+
+void Flag::ValidateAlias(const FlagAlias& alias) {
+ using android::base::EndsWith;
+ using android::base::StartsWith;
+
+ CHECK(StartsWith(alias.name, "-")) << "Flags should start with \"-\"";
+ if (alias.mode == FlagAliasMode::kFlagPrefix) {
+ CHECK(EndsWith(alias.name, "=")) << "Prefix flags shold end with \"=\"";
+ }
+
+ CHECK(!HasAlias(alias)) << "Duplicate flag alias: " << alias.name;
+ if (alias.mode == FlagAliasMode::kFlagConsumesFollowing) {
+ CHECK(!HasAlias({FlagAliasMode::kFlagExact, alias.name}))
+ << "Overlapping flag aliases for " << alias.name;
+ CHECK(!HasAlias({FlagAliasMode::kFlagConsumesArbitrary, alias.name}))
+ << "Overlapping flag aliases for " << alias.name;
+ } else if (alias.mode == FlagAliasMode::kFlagExact) {
+ CHECK(!HasAlias({FlagAliasMode::kFlagConsumesFollowing, alias.name}))
+ << "Overlapping flag aliases for " << alias.name;
+ CHECK(!HasAlias({FlagAliasMode::kFlagConsumesArbitrary, alias.name}))
+ << "Overlapping flag aliases for " << alias.name;
+ } else if (alias.mode == FlagAliasMode::kFlagConsumesArbitrary) {
+ CHECK(!HasAlias({FlagAliasMode::kFlagExact, alias.name}))
+ << "Overlapping flag aliases for " << alias.name;
+ CHECK(!HasAlias({FlagAliasMode::kFlagConsumesFollowing, alias.name}))
+ << "Overlapping flag aliases for " << alias.name;
+ }
+}
+
+Flag& Flag::Alias(const FlagAlias& alias) & {
+ ValidateAlias(alias);
+ aliases_.push_back(alias);
+ return *this;
+}
+Flag Flag::Alias(const FlagAlias& alias) && {
+ ValidateAlias(alias);
+ aliases_.push_back(alias);
+ return *this;
+}
+
+Flag& Flag::Help(const std::string& help) & {
+ help_ = help;
+ return *this;
+}
+Flag Flag::Help(const std::string& help) && {
+ help_ = help;
+ return *this;
+}
+
+Flag& Flag::Getter(std::function<std::string()> fn) & {
+ getter_ = std::move(fn);
+ return *this;
+}
+Flag Flag::Getter(std::function<std::string()> fn) && {
+ getter_ = std::move(fn);
+ return *this;
+}
+
+Flag& Flag::Setter(std::function<bool(const FlagMatch&)> fn) & {
+ setter_ = std::move(fn);
+ return *this;
+}
+Flag Flag::Setter(std::function<bool(const FlagMatch&)> fn) && {
+ setter_ = std::move(fn);
+ return *this;
+}
+
+static bool LikelyFlag(const std::string& next_arg) {
+ return android::base::StartsWith(next_arg, "-");
+}
+
+Flag::FlagProcessResult Flag::Process(
+ const std::string& arg, const std::optional<std::string>& next_arg) const {
+ if (!setter_ && aliases_.size() > 0) {
+ LOG(ERROR) << "No setter for flag with alias " << aliases_[0].name;
+ return FlagProcessResult::kFlagError;
+ }
+ for (auto& alias : aliases_) {
+ switch (alias.mode) {
+ case FlagAliasMode::kFlagConsumesArbitrary:
+ if (arg != alias.name) {
+ continue;
+ }
+ if (!next_arg || LikelyFlag(*next_arg)) {
+ if (!(*setter_)({arg, ""})) {
+ LOG(ERROR) << "Processing \"" << arg << "\" failed";
+ return FlagProcessResult::kFlagError;
+ }
+ return FlagProcessResult::kFlagConsumed;
+ }
+ if (!(*setter_)({arg, *next_arg})) {
+ LOG(ERROR) << "Processing \"" << arg << "\" \"" << *next_arg
+ << "\" failed";
+ return FlagProcessResult::kFlagError;
+ }
+ return FlagProcessResult::kFlagConsumedOnlyFollowing;
+ case FlagAliasMode::kFlagConsumesFollowing:
+ if (arg != alias.name) {
+ continue;
+ }
+ if (!next_arg) {
+ LOG(ERROR) << "Expected an argument after \"" << arg << "\"";
+ return FlagProcessResult::kFlagError;
+ }
+ if (!(*setter_)({arg, *next_arg})) {
+ LOG(ERROR) << "Processing \"" << arg << "\" \"" << *next_arg
+ << "\" failed";
+ return FlagProcessResult::kFlagError;
+ }
+ return FlagProcessResult::kFlagConsumedWithFollowing;
+ case FlagAliasMode::kFlagExact:
+ if (arg != alias.name) {
+ continue;
+ }
+ if (!(*setter_)({arg, arg})) {
+ LOG(ERROR) << "Processing \"" << arg << "\" failed";
+ return FlagProcessResult::kFlagError;
+ }
+ return FlagProcessResult::kFlagConsumed;
+ case FlagAliasMode::kFlagPrefix:
+ if (!android::base::StartsWith(arg, alias.name)) {
+ continue;
+ }
+ if (!(*setter_)({alias.name, arg.substr(alias.name.size())})) {
+ LOG(ERROR) << "Processing \"" << arg << "\" failed";
+ return FlagProcessResult::kFlagError;
+ }
+ return FlagProcessResult::kFlagConsumed;
+ default:
+ LOG(ERROR) << "Unknown flag alias mode: " << (int)alias.mode;
+ return FlagProcessResult::kFlagError;
+ }
+ }
+ return FlagProcessResult::kFlagSkip;
+}
+
+bool Flag::Parse(std::vector<std::string>& arguments) const {
+ for (int i = 0; i < arguments.size();) {
+ std::string arg = arguments[i];
+ std::optional<std::string> next_arg;
+ if (i < arguments.size() - 1) {
+ next_arg = arguments[i + 1];
+ }
+ auto result = Process(arg, next_arg);
+ if (result == FlagProcessResult::kFlagError) {
+ return false;
+ } else if (result == FlagProcessResult::kFlagConsumed) {
+ arguments.erase(arguments.begin() + i);
+ } else if (result == FlagProcessResult::kFlagConsumedWithFollowing) {
+ arguments.erase(arguments.begin() + i, arguments.begin() + i + 2);
+ } else if (result == FlagProcessResult::kFlagConsumedOnlyFollowing) {
+ arguments.erase(arguments.begin() + i + 1, arguments.begin() + i + 2);
+ } else if (result == FlagProcessResult::kFlagSkip) {
+ i++;
+ continue;
+ } else {
+ LOG(ERROR) << "Unknown FlagProcessResult: " << (int)result;
+ return false;
+ }
+ }
+ return true;
+}
+bool Flag::Parse(std::vector<std::string>&& arguments) const {
+ return Parse(static_cast<std::vector<std::string>&>(arguments));
+}
+
+bool Flag::HasAlias(const FlagAlias& test) const {
+ for (const auto& alias : aliases_) {
+ if (alias.mode == test.mode && alias.name == test.name) {
+ return true;
+ }
+ }
+ return false;
+}
+
+static std::string XmlEscape(const std::string& s) {
+ using android::base::StringReplace;
+ return StringReplace(StringReplace(s, "<", "<", true), ">", ">", true);
+}
+
+bool Flag::WriteGflagsCompatXml(std::ostream& out) const {
+ std::unordered_set<std::string> name_guesses;
+ for (const auto& alias : aliases_) {
+ std::string_view name = alias.name;
+ if (!android::base::ConsumePrefix(&name, "-")) {
+ continue;
+ }
+ android::base::ConsumePrefix(&name, "-");
+ if (alias.mode == FlagAliasMode::kFlagExact) {
+ android::base::ConsumePrefix(&name, "no");
+ name_guesses.insert(std::string{name});
+ } else if (alias.mode == FlagAliasMode::kFlagConsumesFollowing) {
+ name_guesses.insert(std::string{name});
+ } else if (alias.mode == FlagAliasMode::kFlagPrefix) {
+ if (!android::base::ConsumeSuffix(&name, "=")) {
+ continue;
+ }
+ name_guesses.insert(std::string{name});
+ }
+ }
+ bool found_alias = false;
+ for (const auto& name : name_guesses) {
+ bool has_bool_aliases =
+ HasAlias({FlagAliasMode::kFlagPrefix, "-" + name + "="}) &&
+ HasAlias({FlagAliasMode::kFlagPrefix, "--" + name + "="}) &&
+ HasAlias({FlagAliasMode::kFlagExact, "-" + name}) &&
+ HasAlias({FlagAliasMode::kFlagExact, "--" + name}) &&
+ HasAlias({FlagAliasMode::kFlagExact, "-no" + name}) &&
+ HasAlias({FlagAliasMode::kFlagExact, "--no" + name});
+ bool has_other_aliases =
+ HasAlias({FlagAliasMode::kFlagPrefix, "-" + name + "="}) &&
+ HasAlias({FlagAliasMode::kFlagPrefix, "--" + name + "="}) &&
+ HasAlias({FlagAliasMode::kFlagConsumesFollowing, "-" + name}) &&
+ HasAlias({FlagAliasMode::kFlagConsumesFollowing, "--" + name});
+ if (has_bool_aliases && has_other_aliases) {
+ LOG(ERROR) << "Expected exactly one of has_bool_aliases and "
+ << "has_other_aliases, got both for \"" << name << "\".";
+ return false;
+ } else if (!has_bool_aliases && !has_other_aliases) {
+ continue;
+ }
+ found_alias = true;
+ // Lifted from external/gflags/src/gflags_reporting.cc:DescribeOneFlagInXML
+ out << "<flag>\n";
+ out << " <file>file.cc</file>\n";
+ out << " <name>" << XmlEscape(name) << "</name>\n";
+ auto help = help_ ? XmlEscape(*help_) : std::string{""};
+ out << " <meaning>" << help << "</meaning>\n";
+ auto value = getter_ ? XmlEscape((*getter_)()) : std::string{""};
+ out << " <default>" << value << "</default>\n";
+ out << " <current>" << value << "</current>\n";
+ out << " <type>" << (has_bool_aliases ? "bool" : "string") << "</type>\n";
+ out << "</flag>\n";
+ }
+ return found_alias;
+}
+
+std::ostream& operator<<(std::ostream& out, const Flag& flag) {
+ out << "[";
+ for (auto it = flag.aliases_.begin(); it != flag.aliases_.end(); it++) {
+ if (it != flag.aliases_.begin()) {
+ out << ", ";
+ }
+ out << *it;
+ }
+ out << "]\n";
+ if (flag.help_) {
+ out << "(" << *flag.help_ << ")\n";
+ }
+ if (flag.getter_) {
+ out << "(Current value: \"" << (*flag.getter_)() << "\")\n";
+ }
+ return out;
+}
+
+std::vector<std::string> ArgsToVec(int argc, char** argv) {
+ std::vector<std::string> args;
+ for (int i = 0; i < argc; i++) {
+ args.push_back(argv[i]);
+ }
+ return args;
+}
+
+bool ParseFlags(const std::vector<Flag>& flags,
+ std::vector<std::string>& args) {
+ for (const auto& flag : flags) {
+ if (!flag.Parse(args)) {
+ return false;
+ }
+ }
+ return true;
+}
+
+bool ParseFlags(const std::vector<Flag>& flags,
+ std::vector<std::string>&& args) {
+ for (const auto& flag : flags) {
+ if (!flag.Parse(args)) {
+ return false;
+ }
+ }
+ return true;
+}
+
+bool WriteGflagsCompatXml(const std::vector<Flag>& flags, std::ostream& out) {
+ for (const auto& flag : flags) {
+ if (!flag.WriteGflagsCompatXml(out)) {
+ return false;
+ }
+ }
+ return true;
+}
+
+Flag HelpFlag(const std::vector<Flag>& flags, const std::string& text) {
+ auto setter = [&](FlagMatch) {
+ if (text.size() > 0) {
+ LOG(INFO) << text;
+ }
+ for (const auto& flag : flags) {
+ LOG(INFO) << flag;
+ }
+ return false;
+ };
+ return Flag()
+ .Alias({FlagAliasMode::kFlagExact, "-help"})
+ .Alias({FlagAliasMode::kFlagExact, "--help"})
+ .Setter(setter);
+}
+
+Flag InvalidFlagGuard() {
+ return Flag()
+ .UnvalidatedAlias({FlagAliasMode::kFlagPrefix, "-"})
+ .Help(
+ "This executable only supports the flags in `-help`. Positional "
+ "arguments may be supported.")
+ .Setter([](const FlagMatch& match) {
+ LOG(ERROR) << "Unknown flag " << match.value;
+ return false;
+ });
+}
+
+Flag UnexpectedArgumentGuard() {
+ return Flag()
+ .UnvalidatedAlias({FlagAliasMode::kFlagPrefix, ""})
+ .Help(
+ "This executable only supports the flags in `-help`. Positional "
+ "arguments are not supported.")
+ .Setter([](const FlagMatch& match) {
+ LOG(ERROR) << "Unexpected argument \"" << match.value << "\"";
+ return false;
+ });
+}
+
+Flag GflagsCompatFlag(const std::string& name) {
+ return Flag()
+ .Alias({FlagAliasMode::kFlagPrefix, "-" + name + "="})
+ .Alias({FlagAliasMode::kFlagPrefix, "--" + name + "="})
+ .Alias({FlagAliasMode::kFlagConsumesFollowing, "-" + name})
+ .Alias({FlagAliasMode::kFlagConsumesFollowing, "--" + name});
+};
+
+Flag GflagsCompatFlag(const std::string& name, std::string& value) {
+ return GflagsCompatFlag(name)
+ .Getter([&value]() { return value; })
+ .Setter([&value](const FlagMatch& match) {
+ value = match.value;
+ return true;
+ });
+}
+
+template <typename T>
+std::optional<T> ParseInteger(const std::string& value) {
+ if (value.size() == 0) {
+ return {};
+ }
+ const char* base = value.c_str();
+ char* end = nullptr;
+ errno = 0;
+ auto r = strtoll(base, &end, /* auto-detect */ 0);
+ if (errno != 0 || end != base + value.size()) {
+ return {};
+ }
+ if (static_cast<T>(r) != r) {
+ return {};
+ }
+ return r;
+}
+
+template <typename T>
+static Flag GflagsCompatNumericFlagGeneric(const std::string& name, T& value) {
+ return GflagsCompatFlag(name)
+ .Getter([&value]() { return std::to_string(value); })
+ .Setter([&value](const FlagMatch& match) {
+ auto parsed = ParseInteger<T>(match.value);
+ if (parsed) {
+ value = *parsed;
+ return true;
+ } else {
+ LOG(ERROR) << "Failed to parse \"" << match.value
+ << "\" as an integer";
+ return false;
+ }
+ });
+}
+
+Flag GflagsCompatFlag(const std::string& name, int32_t& value) {
+ return GflagsCompatNumericFlagGeneric(name, value);
+}
+
+Flag GflagsCompatFlag(const std::string& name, bool& value) {
+ return Flag()
+ .Alias({FlagAliasMode::kFlagPrefix, "-" + name + "="})
+ .Alias({FlagAliasMode::kFlagPrefix, "--" + name + "="})
+ .Alias({FlagAliasMode::kFlagExact, "-" + name})
+ .Alias({FlagAliasMode::kFlagExact, "--" + name})
+ .Alias({FlagAliasMode::kFlagExact, "-no" + name})
+ .Alias({FlagAliasMode::kFlagExact, "--no" + name})
+ .Getter([&value]() { return value ? "true" : "false"; })
+ .Setter([name, &value](const FlagMatch& match) {
+ const auto& key = match.key;
+ if (key == "-" + name || key == "--" + name) {
+ value = true;
+ return true;
+ } else if (key == "-no" + name || key == "--no" + name) {
+ value = false;
+ return true;
+ } else if (key == "-" + name + "=" || key == "--" + name + "=") {
+ if (match.value == "true") {
+ value = true;
+ return true;
+ } else if (match.value == "false") {
+ value = false;
+ return true;
+ } else {
+ LOG(ERROR) << "Unexpected boolean value \"" << match.value << "\""
+ << " for \"" << name << "\"";
+ return false;
+ }
+ }
+ LOG(ERROR) << "Unexpected key \"" << match.key << "\""
+ << " for \"" << name << "\"";
+ return false;
+ });
+};
+
+} // namespace cuttlefish
diff --git a/common/libs/utils/flag_parser.h b/common/libs/utils/flag_parser.h
new file mode 100644
index 0000000..b45c544
--- /dev/null
+++ b/common/libs/utils/flag_parser.h
@@ -0,0 +1,160 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <cstdint>
+#include <functional>
+#include <optional>
+#include <ostream>
+#include <string>
+#include <vector>
+
+/* Support for parsing individual flags out of a larger list of flags. This
+ * supports externally determining the order that flags are evaluated in, and
+ * incrementally integrating with existing flag parsing implementations.
+ *
+ * Start with Flag() or one of the GflagsCompatFlag(...) functions to create new
+ * flags. These flags should be aggregated through the application through some
+ * other mechanism and then evaluated individually with Flag::Parse or together
+ * with ParseFlags on arguments. */
+
+namespace cuttlefish {
+
+/* The matching behavior used with the name in `FlagAlias::name`. */
+enum class FlagAliasMode {
+ /* Match arguments of the form `<name><value>`. In practice, <name> usually
+ * looks like "-flag=" or "--flag=", where the "-" and "=" are included in
+ * parsing. */
+ kFlagPrefix,
+ /* Match arguments of the form `<name>`. In practice, <name> will look like
+ * "-flag" or "--flag". */
+ kFlagExact,
+ /* Match a pair of arguments of the form `<name>` `<value>`. In practice,
+ * <name> will look like "-flag" or "--flag". */
+ kFlagConsumesFollowing,
+ /* Match a sequence of arguments of the form `<name>` `<value>` `<value>`.
+ * This uses heuristics to try to determine when `<value>` is actually another
+ * flag. */
+ kFlagConsumesArbitrary,
+};
+
+/* A single matching rule for a `Flag`. One `Flag` can have multiple rules. */
+struct FlagAlias {
+ FlagAliasMode mode;
+ std::string name;
+};
+
+std::ostream& operator<<(std::ostream&, const FlagAlias&);
+
+/* A successful match in an argument list from a `FlagAlias` inside a `Flag`.
+ * The `key` value corresponds to `FlagAlias::name`. For a match of
+ * `FlagAliasMode::kFlagExact`, `key` and `value` will both be the `name`. */
+struct FlagMatch {
+ std::string key;
+ std::string value;
+};
+
+class Flag {
+ public:
+ /* Add an alias that triggers matches and calls to the `Setter` function. */
+ Flag& Alias(const FlagAlias& alias) &;
+ Flag Alias(const FlagAlias& alias) &&;
+ /* Set help text, visible in the class ostream writer method. Optional. */
+ Flag& Help(const std::string&) &;
+ Flag Help(const std::string&) &&;
+ /* Set a loader that displays the current value in help text. Optional. */
+ Flag& Getter(std::function<std::string()>) &;
+ Flag Getter(std::function<std::string()>) &&;
+ /* Set the callback for matches. The callback be invoked multiple times. */
+ Flag& Setter(std::function<bool(const FlagMatch&)>) &;
+ Flag Setter(std::function<bool(const FlagMatch&)>) &&;
+
+ /* Examines a list of arguments, removing any matches from the list and
+ * invoking the `Setter` for every match. Returns `false` if the callback ever
+ * returns `false`. Non-matches are left in place. */
+ bool Parse(std::vector<std::string>& flags) const;
+ bool Parse(std::vector<std::string>&& flags) const;
+
+ /* Write gflags `--helpxml` style output for a string-type flag. */
+ bool WriteGflagsCompatXml(std::ostream&) const;
+
+ private:
+ /* Reports whether `Process` wants to consume zero, one, or two arguments. */
+ enum class FlagProcessResult {
+ /* Error in handling a flag, exit flag handling with an error result. */
+ kFlagError,
+ kFlagSkip, /* Flag skipped; consume no arguments. */
+ kFlagConsumed, /* Flag processed; consume one argument. */
+ kFlagConsumedWithFollowing, /* Flag processed; consume 2 arguments. */
+ kFlagConsumedOnlyFollowing, /* Flag processed; consume next argument. */
+ };
+
+ void ValidateAlias(const FlagAlias& alias);
+ Flag& UnvalidatedAlias(const FlagAlias& alias) &;
+ Flag UnvalidatedAlias(const FlagAlias& alias) &&;
+
+ /* Attempt to match a single argument. */
+ FlagProcessResult Process(const std::string& argument,
+ const std::optional<std::string>& next_arg) const;
+
+ bool HasAlias(const FlagAlias&) const;
+
+ friend std::ostream& operator<<(std::ostream&, const Flag&);
+ friend Flag InvalidFlagGuard();
+ friend Flag UnexpectedArgumentGuard();
+
+ std::vector<FlagAlias> aliases_;
+ std::optional<std::string> help_;
+ std::optional<std::function<std::string()>> getter_;
+ std::optional<std::function<bool(const FlagMatch&)>> setter_;
+};
+
+std::ostream& operator<<(std::ostream&, const Flag&);
+
+std::vector<std::string> ArgsToVec(int argc, char** argv);
+
+/* Handles a list of flags. Flags are matched in the order given in case two
+ * flags match the same argument. Matched flags are removed, leaving only
+ * unmatched arguments. */
+bool ParseFlags(const std::vector<Flag>& flags, std::vector<std::string>& args);
+bool ParseFlags(const std::vector<Flag>& flags, std::vector<std::string>&&);
+
+bool WriteGflagsCompatXml(const std::vector<Flag>&, std::ostream&);
+
+/* If any of these are used, they should be evaluated after all other flags, and
+ * in the order defined here (help before invalid flags, invalid flags before
+ * unexpected arguments). */
+
+/* If a "-help" or "--help" flag is present, prints all the flags and fails. */
+Flag HelpFlag(const std::vector<Flag>& flags, const std::string& text = "");
+/* Catches unrecognized arguments that begin with `-`, and errors out. This
+ * effectively denies unknown flags. */
+Flag InvalidFlagGuard();
+/* Catches any arguments not extracted by other Flag matchers and errors out.
+ * This effectively denies unknown flags and any positional arguments. */
+Flag UnexpectedArgumentGuard();
+
+// Create a flag resembling a gflags argument of the given type. This includes
+// "-[-]flag=*",support for all types, "-[-]noflag" support for booleans, and
+// "-flag *", "--flag *", support for other types. The value passed in the flag
+// is saved to the defined reference.
+Flag GflagsCompatFlag(const std::string& name);
+Flag GflagsCompatFlag(const std::string& name, std::string& value);
+Flag GflagsCompatFlag(const std::string& name, std::int32_t& value);
+Flag GflagsCompatFlag(const std::string& name, bool& value);
+
+} // namespace cuttlefish
diff --git a/common/libs/utils/flag_parser_test.cpp b/common/libs/utils/flag_parser_test.cpp
new file mode 100644
index 0000000..c9798ee
--- /dev/null
+++ b/common/libs/utils/flag_parser_test.cpp
@@ -0,0 +1,324 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <common/libs/utils/flag_parser.h>
+
+#include <gtest/gtest.h>
+#include <libxml/tree.h>
+#include <map>
+#include <optional>
+#include <sstream>
+#include <string>
+#include <vector>
+
+namespace cuttlefish {
+
+TEST(FlagParser, DuplicateAlias) {
+ FlagAlias alias = {FlagAliasMode::kFlagExact, "--flag"};
+ ASSERT_DEATH({ Flag().Alias(alias).Alias(alias); }, "Duplicate flag alias");
+}
+
+TEST(FlagParser, ConflictingAlias) {
+ FlagAlias exact_alias = {FlagAliasMode::kFlagExact, "--flag"};
+ FlagAlias following_alias = {FlagAliasMode::kFlagConsumesFollowing, "--flag"};
+ ASSERT_DEATH({ Flag().Alias(exact_alias).Alias(following_alias); },
+ "Overlapping flag aliases");
+}
+
+TEST(FlagParser, StringFlag) {
+ std::string value;
+ auto flag = GflagsCompatFlag("myflag", value);
+ ASSERT_TRUE(flag.Parse({"-myflag=a"}));
+ ASSERT_EQ(value, "a");
+ ASSERT_TRUE(flag.Parse({"--myflag=b"}));
+ ASSERT_EQ(value, "b");
+ ASSERT_TRUE(flag.Parse({"-myflag", "c"}));
+ ASSERT_EQ(value, "c");
+ ASSERT_TRUE(flag.Parse({"--myflag", "d"}));
+ ASSERT_EQ(value, "d");
+ ASSERT_TRUE(flag.Parse({"--myflag="}));
+ ASSERT_EQ(value, "");
+}
+
+std::optional<std::map<std::string, std::string>> flagXml(const Flag& f) {
+ std::stringstream xml_stream;
+ if (!f.WriteGflagsCompatXml(xml_stream)) {
+ return {};
+ }
+ auto xml = xml_stream.str();
+ // Holds all memory for the parsed structure.
+ std::unique_ptr<xmlDoc, xmlFreeFunc> doc(
+ xmlReadMemory(xml.c_str(), xml.size(), nullptr, nullptr, 0), xmlFree);
+ if (!doc) {
+ return {};
+ }
+ xmlNodePtr root_element = xmlDocGetRootElement(doc.get());
+ std::map<std::string, std::string> elements_map;
+ for (auto elem = root_element->children; elem != nullptr; elem = elem->next) {
+ if (elem->type != xmlElementType::XML_ELEMENT_NODE) {
+ continue;
+ }
+ elements_map[(char*)elem->name] = "";
+ if (elem->children == nullptr) {
+ continue;
+ }
+ if (elem->children->type != XML_TEXT_NODE) {
+ continue;
+ }
+ elements_map[(char*)elem->name] = (char*)elem->children->content;
+ }
+ return elements_map;
+}
+
+TEST(FlagParser, GflagsIncompatibleFlag) {
+ auto flag = Flag().Alias({FlagAliasMode::kFlagExact, "--flag"});
+ ASSERT_FALSE(flagXml(flag));
+}
+
+TEST(FlagParser, StringFlagXml) {
+ std::string value = "somedefault";
+ auto flag = GflagsCompatFlag("myflag", value).Help("somehelp");
+ auto xml = flagXml(flag);
+ ASSERT_TRUE(xml);
+ ASSERT_NE((*xml)["file"], "");
+ ASSERT_EQ((*xml)["name"], "myflag");
+ ASSERT_EQ((*xml)["meaning"], "somehelp");
+ ASSERT_EQ((*xml)["default"], "somedefault");
+ ASSERT_EQ((*xml)["current"], "somedefault");
+ ASSERT_EQ((*xml)["type"], "string");
+}
+
+TEST(FlagParser, RepeatedStringFlag) {
+ std::string value;
+ auto flag = GflagsCompatFlag("myflag", value);
+ ASSERT_TRUE(flag.Parse({"-myflag=a", "--myflag", "b"}));
+ ASSERT_EQ(value, "b");
+}
+
+TEST(FlagParser, RepeatedListFlag) {
+ std::vector<std::string> elems;
+ auto flag = GflagsCompatFlag("myflag");
+ flag.Setter([&elems](const FlagMatch& match) {
+ elems.push_back(match.value);
+ return true;
+ });
+ ASSERT_TRUE(flag.Parse({"-myflag=a", "--myflag", "b"}));
+ ASSERT_EQ(elems, (std::vector<std::string>{"a", "b"}));
+}
+
+TEST(FlagParser, FlagRemoval) {
+ std::string value;
+ auto flag = GflagsCompatFlag("myflag", value);
+ std::vector<std::string> flags = {"-myflag=a", "-otherflag=c"};
+ ASSERT_TRUE(flag.Parse(flags));
+ ASSERT_EQ(value, "a");
+ ASSERT_EQ(flags, std::vector<std::string>{"-otherflag=c"});
+ flags = {"-otherflag=a", "-myflag=c"};
+ ASSERT_TRUE(flag.Parse(flags));
+ ASSERT_EQ(value, "c");
+ ASSERT_EQ(flags, std::vector<std::string>{"-otherflag=a"});
+}
+
+TEST(FlagParser, IntFlag) {
+ std::int32_t value = 0;
+ auto flag = GflagsCompatFlag("myflag", value);
+ ASSERT_TRUE(flag.Parse({"-myflag=5"}));
+ ASSERT_EQ(value, 5);
+ ASSERT_TRUE(flag.Parse({"--myflag=6"}));
+ ASSERT_EQ(value, 6);
+ ASSERT_TRUE(flag.Parse({"-myflag", "7"}));
+ ASSERT_EQ(value, 7);
+ ASSERT_TRUE(flag.Parse({"--myflag", "8"}));
+ ASSERT_EQ(value, 8);
+}
+
+TEST(FlagParser, IntFlagXml) {
+ int value = 5;
+ auto flag = GflagsCompatFlag("myflag", value).Help("somehelp");
+ auto xml = flagXml(flag);
+ ASSERT_TRUE(xml);
+ ASSERT_NE((*xml)["file"], "");
+ ASSERT_EQ((*xml)["name"], "myflag");
+ ASSERT_EQ((*xml)["meaning"], "somehelp");
+ ASSERT_EQ((*xml)["default"], "5");
+ ASSERT_EQ((*xml)["current"], "5");
+ ASSERT_EQ((*xml)["type"], "string");
+}
+
+TEST(FlagParser, BoolFlag) {
+ bool value = false;
+ auto flag = GflagsCompatFlag("myflag", value);
+ ASSERT_TRUE(flag.Parse({"-myflag"}));
+ ASSERT_TRUE(value);
+
+ value = false;
+ ASSERT_TRUE(flag.Parse({"--myflag"}));
+ ASSERT_TRUE(value);
+
+ value = false;
+ ASSERT_TRUE(flag.Parse({"-myflag=true"}));
+ ASSERT_TRUE(value);
+
+ value = false;
+ ASSERT_TRUE(flag.Parse({"--myflag=true"}));
+ ASSERT_TRUE(value);
+
+ value = true;
+ ASSERT_TRUE(flag.Parse({"-nomyflag"}));
+ ASSERT_FALSE(value);
+
+ value = true;
+ ASSERT_TRUE(flag.Parse({"--nomyflag"}));
+ ASSERT_FALSE(value);
+
+ value = true;
+ ASSERT_TRUE(flag.Parse({"-myflag=false"}));
+ ASSERT_FALSE(value);
+
+ value = true;
+ ASSERT_TRUE(flag.Parse({"--myflag=false"}));
+ ASSERT_FALSE(value);
+
+ ASSERT_FALSE(flag.Parse({"--myflag=nonsense"}));
+}
+
+TEST(FlagParser, BoolFlagXml) {
+ bool value = true;
+ auto flag = GflagsCompatFlag("myflag", value).Help("somehelp");
+ auto xml = flagXml(flag);
+ ASSERT_TRUE(xml);
+ ASSERT_NE((*xml)["file"], "");
+ ASSERT_EQ((*xml)["name"], "myflag");
+ ASSERT_EQ((*xml)["meaning"], "somehelp");
+ ASSERT_EQ((*xml)["default"], "true");
+ ASSERT_EQ((*xml)["current"], "true");
+ ASSERT_EQ((*xml)["type"], "bool");
+}
+
+TEST(FlagParser, StringIntFlag) {
+ std::int32_t int_value = 0;
+ std::string string_value;
+ auto int_flag = GflagsCompatFlag("int", int_value);
+ auto string_flag = GflagsCompatFlag("string", string_value);
+ std::vector<Flag> flags = {int_flag, string_flag};
+ ASSERT_TRUE(ParseFlags(flags, {"-int=5", "-string=a"}));
+ ASSERT_EQ(int_value, 5);
+ ASSERT_EQ(string_value, "a");
+ ASSERT_TRUE(ParseFlags(flags, {"--int=6", "--string=b"}));
+ ASSERT_EQ(int_value, 6);
+ ASSERT_EQ(string_value, "b");
+ ASSERT_TRUE(ParseFlags(flags, {"-int", "7", "-string", "c"}));
+ ASSERT_EQ(int_value, 7);
+ ASSERT_EQ(string_value, "c");
+ ASSERT_TRUE(ParseFlags(flags, {"--int", "8", "--string", "d"}));
+ ASSERT_EQ(int_value, 8);
+ ASSERT_EQ(string_value, "d");
+}
+
+TEST(FlagParser, InvalidStringFlag) {
+ std::string value;
+ auto flag = GflagsCompatFlag("myflag", value);
+ ASSERT_FALSE(flag.Parse({"-myflag"}));
+ ASSERT_FALSE(flag.Parse({"--myflag"}));
+}
+
+TEST(FlagParser, InvalidIntFlag) {
+ int value;
+ auto flag = GflagsCompatFlag("myflag", value);
+ ASSERT_FALSE(flag.Parse({"-myflag"}));
+ ASSERT_FALSE(flag.Parse({"--myflag"}));
+ ASSERT_FALSE(flag.Parse({"-myflag=abc"}));
+ ASSERT_FALSE(flag.Parse({"--myflag=def"}));
+ ASSERT_FALSE(flag.Parse({"-myflag", "abc"}));
+ ASSERT_FALSE(flag.Parse({"--myflag", "def"}));
+}
+
+TEST(FlagParser, InvalidFlagGuard) {
+ auto flag = InvalidFlagGuard();
+ ASSERT_TRUE(flag.Parse({}));
+ ASSERT_TRUE(flag.Parse({"positional"}));
+ ASSERT_TRUE(flag.Parse({"positional", "positional2"}));
+ ASSERT_FALSE(flag.Parse({"-flag"}));
+ ASSERT_FALSE(flag.Parse({"-"}));
+}
+
+TEST(FlagParser, UnexpectedArgumentGuard) {
+ auto flag = UnexpectedArgumentGuard();
+ ASSERT_TRUE(flag.Parse({}));
+ ASSERT_FALSE(flag.Parse({"positional"}));
+ ASSERT_FALSE(flag.Parse({"positional", "positional2"}));
+ ASSERT_FALSE(flag.Parse({"-flag"}));
+ ASSERT_FALSE(flag.Parse({"-"}));
+}
+
+class FlagConsumesArbitraryTest : public ::testing::Test {
+ protected:
+ void SetUp() override {
+ elems_.clear();
+ flag_ = Flag()
+ .Alias({FlagAliasMode::kFlagConsumesArbitrary, "--flag"})
+ .Setter([this](const FlagMatch& match) {
+ elems_.push_back(match.value);
+ return true;
+ });
+ }
+ Flag flag_;
+ std::vector<std::string> elems_;
+};
+
+TEST_F(FlagConsumesArbitraryTest, NoValues) {
+ std::vector<std::string> inputs = {"--flag"};
+ ASSERT_TRUE(flag_.Parse(inputs));
+ ASSERT_EQ(inputs, (std::vector<std::string>{}));
+ ASSERT_EQ(elems_, (std::vector<std::string>{""}));
+}
+
+TEST_F(FlagConsumesArbitraryTest, OneValue) {
+ std::vector<std::string> inputs = {"--flag", "value"};
+ ASSERT_TRUE(flag_.Parse(inputs));
+ ASSERT_EQ(inputs, (std::vector<std::string>{}));
+ ASSERT_EQ(elems_, (std::vector<std::string>{"value", ""}));
+}
+
+TEST_F(FlagConsumesArbitraryTest, TwoValues) {
+ std::vector<std::string> inputs = {"--flag", "value1", "value2"};
+ ASSERT_TRUE(flag_.Parse(inputs));
+ ASSERT_EQ(inputs, (std::vector<std::string>{}));
+ ASSERT_EQ(elems_, (std::vector<std::string>{"value1", "value2", ""}));
+}
+
+TEST_F(FlagConsumesArbitraryTest, NoValuesOtherFlag) {
+ std::vector<std::string> inputs = {"--flag", "--otherflag"};
+ ASSERT_TRUE(flag_.Parse(inputs));
+ ASSERT_EQ(inputs, (std::vector<std::string>{"--otherflag"}));
+ ASSERT_EQ(elems_, (std::vector<std::string>{""}));
+}
+
+TEST_F(FlagConsumesArbitraryTest, OneValueOtherFlag) {
+ std::vector<std::string> inputs = {"--flag", "value", "--otherflag"};
+ ASSERT_TRUE(flag_.Parse(inputs));
+ ASSERT_EQ(inputs, (std::vector<std::string>{"--otherflag"}));
+ ASSERT_EQ(elems_, (std::vector<std::string>{"value", ""}));
+}
+
+TEST_F(FlagConsumesArbitraryTest, TwoValuesOtherFlag) {
+ std::vector<std::string> inputs = {"--flag", "v1", "v2", "--otherflag"};
+ ASSERT_TRUE(flag_.Parse(inputs));
+ ASSERT_EQ(inputs, (std::vector<std::string>{"--otherflag"}));
+ ASSERT_EQ(elems_, (std::vector<std::string>{"v1", "v2", ""}));
+}
+
+} // namespace cuttlefish
diff --git a/common/libs/utils/network.cpp b/common/libs/utils/network.cpp
index d1f5f59..9727584 100644
--- a/common/libs/utils/network.cpp
+++ b/common/libs/utils/network.cpp
@@ -16,32 +16,42 @@
#include "common/libs/utils/network.h"
-#include <arpa/inet.h>
-#include <linux/if.h>
+// Kernel headers don't mix well with userspace headers, but there is no
+// userspace header that provides the if_tun.h #defines. Include the kernel
+// header, but move conflicting definitions out of the way using macros.
+#define ethhdr __kernel_ethhdr
#include <linux/if_tun.h>
+#undef ethhdr
+
+#include <endian.h>
+#include <fcntl.h>
+#include <linux/if_ether.h>
#include <linux/types.h>
-#include <linux/if_packet.h>
+#include <net/ethernet.h>
+#include <net/if.h>
#include <netinet/ip.h>
#include <netinet/udp.h>
-#include <netinet/ether.h>
-#include <string.h>
+#include <cstdlib>
+#include <cstring>
+#include <functional>
+#include <ios>
+#include <memory>
+#include <ostream>
+#include <set>
+#include <string>
+#include <utility>
+#include <vector>
+
+#include <android-base/logging.h>
#include <android-base/strings.h>
-#include "android-base/logging.h"
#include "common/libs/fs/shared_buf.h"
-#include "common/libs/utils/environment.h"
#include "common/libs/utils/subprocess.h"
namespace cuttlefish {
namespace {
-static std::string DefaultHostArtifactsPath(const std::string& file_name) {
- return (StringFromEnv("ANDROID_HOST_OUT", StringFromEnv("HOME", ".")) +
- "/") +
- file_name;
-}
-
// This should be the size of virtio_net_hdr_v1, from linux/virtio_net.h, but
// the version of that header that ships with android in Pie does not include
// that struct (it was added in Q).
@@ -96,42 +106,28 @@
return tap_fd;
}
- if (HostArch() == Arch::Arm64) {
- auto tapsetiff_path = DefaultHostArtifactsPath("bin/tapsetiff");
- Command cmd(tapsetiff_path);
- cmd.AddParameter(tap_fd);
- cmd.AddParameter(interface_name.c_str());
- int ret = cmd.Start().Wait();
- if (ret != 0) {
- LOG(ERROR) << "Unable to run tapsetiff.py. Exited with status " << ret;
- tap_fd->Close();
- return SharedFD();
- }
- } else {
- struct ifreq ifr;
- memset(&ifr, 0, sizeof(ifr));
- ifr.ifr_flags = IFF_TAP | IFF_NO_PI | IFF_VNET_HDR;
- strncpy(ifr.ifr_name, interface_name.c_str(), IFNAMSIZ);
+ struct ifreq ifr;
+ memset(&ifr, 0, sizeof(ifr));
+ ifr.ifr_flags = IFF_TAP | IFF_NO_PI | IFF_VNET_HDR;
+ strncpy(ifr.ifr_name, interface_name.c_str(), IFNAMSIZ);
- int err = tap_fd->Ioctl(TUNSETIFF, &ifr);
- if (err < 0) {
- LOG(ERROR) << "Unable to connect to " << interface_name
- << " tap interface: " << tap_fd->StrError();
- tap_fd->Close();
- return SharedFD();
- }
-
- // The interface's configuration may have been modified or just not set
- // correctly on creation. While qemu checks this and enforces the right
- // configuration, crosvm does not, so it needs to be set before it's passed to
- // it.
- tap_fd->Ioctl(TUNSETOFFLOAD,
- reinterpret_cast<void*>(TUN_F_CSUM | TUN_F_UFO | TUN_F_TSO4 |
- TUN_F_TSO6));
- int len = SIZE_OF_VIRTIO_NET_HDR_V1;
- tap_fd->Ioctl(TUNSETVNETHDRSZ, &len);
+ int err = tap_fd->Ioctl(TUNSETIFF, &ifr);
+ if (err < 0) {
+ LOG(ERROR) << "Unable to connect to " << interface_name
+ << " tap interface: " << tap_fd->StrError();
+ tap_fd->Close();
+ return SharedFD();
}
+ // The interface's configuration may have been modified or just not set
+ // correctly on creation. While qemu checks this and enforces the right
+ // configuration, crosvm does not, so it needs to be set before it's passed to
+ // it.
+ tap_fd->Ioctl(TUNSETOFFLOAD,
+ reinterpret_cast<void*>(TUN_F_CSUM | TUN_F_UFO | TUN_F_TSO4 |
+ TUN_F_TSO6));
+ int len = SIZE_OF_VIRTIO_NET_HDR_V1;
+ tap_fd->Ioctl(TUNSETVNETHDRSZ, &len);
return tap_fd;
}
@@ -139,9 +135,9 @@
Command cmd("/bin/bash");
cmd.AddParameter("-c");
cmd.AddParameter("egrep -h -e \"^iff:.*\" /proc/*/fdinfo/*");
- std::string stdin, stdout, stderr;
- RunWithManagedStdio(std::move(cmd), &stdin, &stdout, &stderr);
- auto lines = android::base::Split(stdout, "\n");
+ std::string stdin_str, stdout_str, stderr_str;
+ RunWithManagedStdio(std::move(cmd), &stdin_str, &stdout_str, &stderr_str);
+ auto lines = android::base::Split(stdout_str, "\n");
std::set<std::string> tap_interfaces;
for (const auto& line : lines) {
if (line == "") {
@@ -310,4 +306,25 @@
return true;
}
+bool ReleaseDhcpLeases(const std::string& lease_path, SharedFD tap_fd,
+ const std::uint8_t dhcp_server_ip[4]) {
+ auto lease_file_fd = SharedFD::Open(lease_path, O_RDONLY);
+ if (!lease_file_fd->IsOpen()) {
+ LOG(ERROR) << "Could not open leases file \"" << lease_path << '"';
+ return false;
+ }
+ bool success = true;
+ auto dhcp_leases = ParseDnsmasqLeases(lease_file_fd);
+ for (auto& lease : dhcp_leases) {
+ if (!ReleaseDhcp4(tap_fd, lease.mac_address, lease.ip_address,
+ dhcp_server_ip)) {
+ LOG(ERROR) << "Failed to release " << lease;
+ success = false;
+ } else {
+ LOG(INFO) << "Successfully dropped " << lease;
+ }
+ }
+ return success;
+}
+
} // namespace cuttlefish
diff --git a/common/libs/utils/network.h b/common/libs/utils/network.h
index 64b562e..985476e 100644
--- a/common/libs/utils/network.h
+++ b/common/libs/utils/network.h
@@ -15,7 +15,8 @@
*/
#pragma once
-#include <cstddef>
+#include <cstdint>
+#include <ostream>
#include <set>
#include <string>
#include <vector>
@@ -49,4 +50,6 @@
const std::uint8_t ip_address[4],
const std::uint8_t dhcp_server_ip[4]);
+bool ReleaseDhcpLeases(const std::string& lease_path, SharedFD tap_fd,
+ const std::uint8_t dhcp_server_ip[4]);
}
diff --git a/common/libs/utils/result.h b/common/libs/utils/result.h
new file mode 100644
index 0000000..2b732e4
--- /dev/null
+++ b/common/libs/utils/result.h
@@ -0,0 +1,168 @@
+//
+// Copyright (C) 2022 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#pragma once
+
+#include <optional>
+#include <type_traits>
+
+#include <android-base/logging.h>
+#include <android-base/result.h> // IWYU pragma: export
+
+namespace cuttlefish {
+
+using android::base::Result;
+
+#define CF_ERR_MSG() \
+ " at " << __FILE__ << ":" << __LINE__ << "\n" \
+ << " in " << __PRETTY_FUNCTION__
+
+/**
+ * Error return macro that includes the location in the file in the error
+ * message. Use CF_ERRNO when including information from errno, otherwise use
+ * the base CF_ERR macro.
+ *
+ * Example usage:
+ *
+ * if (mkdir(path.c_str()) != 0) {
+ * return CF_ERRNO("mkdir(\"" << path << "\") failed: "
+ * << strerror(errno));
+ * }
+ *
+ * This will return an error with the text
+ *
+ * mkdir(...) failed: ...
+ * at path/to/file.cpp:50
+ * in Result<std::string> MyFunction()
+ */
+#define CF_ERR(MSG) android::base::Error() << MSG << "\n" << CF_ERR_MSG()
+#define CF_ERRNO(MSG) \
+ android::base::ErrnoError() << MSG << "\n" \
+ << CF_ERR_MSG() << "\n with errno " << errno
+
+template <typename T>
+T OutcomeDereference(std::optional<T>&& value) {
+ return std::move(*value);
+}
+
+template <typename T>
+typename std::conditional_t<std::is_void_v<T>, bool, T> OutcomeDereference(
+ Result<T>&& result) {
+ if constexpr (std::is_void<T>::value) {
+ return result.ok();
+ } else {
+ return std::move(*result);
+ }
+}
+
+template <typename T>
+typename std::enable_if<std::is_convertible_v<T, bool>, T>::type
+OutcomeDereference(T&& value) {
+ return std::forward<T>(value);
+}
+
+inline bool TypeIsSuccess(bool value) { return value; }
+
+template <typename T>
+bool TypeIsSuccess(std::optional<T>& value) {
+ return value.has_value();
+}
+
+template <typename T>
+bool TypeIsSuccess(Result<T>& value) {
+ return value.ok();
+}
+
+template <typename T>
+bool TypeIsSuccess(Result<T>&& value) {
+ return value.ok();
+}
+
+inline auto ErrorFromType(bool) {
+ return (android::base::Error() << "Received `false`").str();
+}
+
+template <typename T>
+inline auto ErrorFromType(std::optional<T>) {
+ return (android::base::Error() << "Received empty optional").str();
+}
+
+template <typename T>
+auto ErrorFromType(Result<T>& value) {
+ return value.error();
+}
+
+template <typename T>
+auto ErrorFromType(Result<T>&& value) {
+ return value.error();
+}
+
+#define CF_EXPECT_OVERLOAD(_1, _2, NAME, ...) NAME
+
+#define CF_EXPECT2(RESULT, MSG) \
+ ({ \
+ decltype(RESULT)&& macro_intermediate_result = RESULT; \
+ if (!TypeIsSuccess(macro_intermediate_result)) { \
+ return android::base::Error() \
+ << ErrorFromType(macro_intermediate_result) << "\n" \
+ << MSG << "\n" \
+ << CF_ERR_MSG() << "\n" \
+ << " for CF_EXPECT(" << #RESULT << ")"; \
+ }; \
+ OutcomeDereference(std::move(macro_intermediate_result)); \
+ })
+
+#define CF_EXPECT1(RESULT) CF_EXPECT2(RESULT, "Received error")
+
+/**
+ * Error propagation macro that can be used as an expression.
+ *
+ * The first argument can be either a Result or a type that is convertible to
+ * a boolean. A successful result will return the value inside the result, or
+ * a conversion to a `true` value will return the unconverted value. This is
+ * useful for e.g. pointers which can be tested through boolean conversion.
+ *
+ * In the failure case, this macro will return from the containing function
+ * with a failing Result. The failing result will include information about the
+ * call site, details from the optional second argument if given, and details
+ * from the failing inner expression if it is a Result.
+ *
+ * This macro must be invoked only in functions that return a Result.
+ *
+ * Example usage:
+ *
+ * Result<std::string> CreateTempDir();
+ *
+ * Result<std::string> CreatePopulatedTempDir() {
+ * std::string dir = CF_EXPECT(CreateTempDir(), "Failed to create dir");
+ * // Do something with dir
+ * return dir;
+ * }
+ *
+ * If CreateTempDir fails, the function will returna Result with an error
+ * message that looks like
+ *
+ * Internal error
+ * at /path/to/otherfile.cpp:50
+ * in Result<std::string> CreateTempDir()
+ * Failed to create dir
+ * at /path/to/file.cpp:81:
+ * in Result<std::string> CreatePopulatedTempDir()
+ * for CF_EXPECT(CreateTempDir())
+ */
+#define CF_EXPECT(...) \
+ CF_EXPECT_OVERLOAD(__VA_ARGS__, CF_EXPECT2, CF_EXPECT1)(__VA_ARGS__)
+
+} // namespace cuttlefish
diff --git a/common/libs/utils/shared_fd_flag.cpp b/common/libs/utils/shared_fd_flag.cpp
new file mode 100644
index 0000000..b894147
--- /dev/null
+++ b/common/libs/utils/shared_fd_flag.cpp
@@ -0,0 +1,56 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#include "common/libs/utils/shared_fd_flag.h"
+
+#include <unistd.h>
+
+#include <functional>
+#include <limits>
+#include <memory>
+#include <ostream>
+#include <string>
+
+#include <android-base/logging.h>
+#include <android-base/parseint.h>
+
+#include "common/libs/fs/shared_fd.h"
+#include "common/libs/utils/flag_parser.h"
+
+namespace cuttlefish {
+
+static bool Set(const FlagMatch& match, SharedFD& out) {
+ int raw_fd;
+ if (!android::base::ParseInt(match.value.c_str(), &raw_fd)) {
+ LOG(ERROR) << "Failed to parse value \"" << match.value
+ << "\" for fd flag \"" << match.key << "\"";
+ return false;
+ }
+ out = SharedFD::Dup(raw_fd);
+ if (out->IsOpen()) {
+ close(raw_fd);
+ }
+ return true;
+}
+
+Flag SharedFDFlag(SharedFD& out) {
+ return Flag().Setter([&](const FlagMatch& mat) { return Set(mat, out); });
+}
+Flag SharedFDFlag(const std::string& name, SharedFD& out) {
+ return GflagsCompatFlag(name).Setter(
+ [&out](const FlagMatch& mat) { return Set(mat, out); });
+}
+
+} // namespace cuttlefish
diff --git a/common/libs/utils/size_utils.cpp b/common/libs/utils/shared_fd_flag.h
similarity index 69%
rename from common/libs/utils/size_utils.cpp
rename to common/libs/utils/shared_fd_flag.h
index 9f25445..33b1555 100644
--- a/common/libs/utils/size_utils.cpp
+++ b/common/libs/utils/shared_fd_flag.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2018 The Android Open Source Project
+ * Copyright (C) 2021 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -13,16 +13,16 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
+#pragma once
-#include "common/libs/utils/size_utils.h"
+#include <string>
-#include <unistd.h>
+#include "common/libs/fs/shared_fd.h"
+#include "common/libs/utils/flag_parser.h"
namespace cuttlefish {
-uint64_t AlignToPowerOf2(uint64_t val, uint8_t align_log) {
- uint64_t align = 1ULL << align_log;
- return ((val + (align - 1)) / align) * align;
-}
+Flag SharedFDFlag(SharedFD& out);
+Flag SharedFDFlag(const std::string& name, SharedFD& out);
} // namespace cuttlefish
diff --git a/common/libs/utils/size_utils.h b/common/libs/utils/size_utils.h
index 625cdd8..71bfea6 100644
--- a/common/libs/utils/size_utils.h
+++ b/common/libs/utils/size_utils.h
@@ -26,6 +26,9 @@
constexpr int PARTITION_SIZE_SHIFT = 12;
// Returns the smallest multiple of 2^align_log greater than or equal to val.
-uint64_t AlignToPowerOf2(uint64_t val, uint8_t align_log);
+constexpr uint64_t AlignToPowerOf2(uint64_t val, uint8_t align_log) {
+ uint64_t align = 1ULL << align_log;
+ return ((val + (align - 1)) / align) * align;
+}
} // namespace cuttlefish
diff --git a/common/libs/utils/socket2socket_proxy.cpp b/common/libs/utils/socket2socket_proxy.cpp
new file mode 100644
index 0000000..e3d7343
--- /dev/null
+++ b/common/libs/utils/socket2socket_proxy.cpp
@@ -0,0 +1,77 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "common/libs/utils/socket2socket_proxy.h"
+
+#include <sys/socket.h>
+
+#include <cstring>
+#include <functional>
+#include <memory>
+#include <ostream>
+#include <string>
+#include <thread>
+
+#include <android-base/logging.h>
+
+namespace cuttlefish {
+namespace {
+
+void Forward(const std::string& label, SharedFD from, SharedFD to) {
+ auto success = to->CopyAllFrom(*from);
+ if (!success) {
+ if (from->GetErrno()) {
+ LOG(ERROR) << label << ": Error reading: " << from->StrError();
+ }
+ if (to->GetErrno()) {
+ LOG(ERROR) << label << ": Error writing: " << to->StrError();
+ }
+ }
+ to->Shutdown(SHUT_WR);
+ LOG(DEBUG) << label << " completed";
+}
+
+void SetupProxying(SharedFD client, SharedFD target) {
+ std::thread([client, target]() {
+ std::thread client2target(Forward, "client2target", client, target);
+ Forward("target2client", target, client);
+ client2target.join();
+ // The actual proxying is handled in a detached thread so that this function
+ // returns immediately
+ }).detach();
+}
+
+} // namespace
+
+void Proxy(SharedFD server, std::function<SharedFD()> conn_factory) {
+ while (server->IsOpen()) {
+ auto client = SharedFD::Accept(*server);
+ if (!client->IsOpen()) {
+ LOG(ERROR) << "Failed to accept connection in server: "
+ << client->StrError();
+ continue;
+ }
+ auto target = conn_factory();
+ if (target->IsOpen()) {
+ SetupProxying(client, target);
+ }
+ // The client will close when it goes out of scope here if the target didn't
+ // open.
+ }
+ LOG(INFO) << "Server closed: " << server->StrError();
+}
+
+} // namespace cuttlefish
diff --git a/common/libs/utils/socket2socket_proxy.h b/common/libs/utils/socket2socket_proxy.h
new file mode 100644
index 0000000..28e17b1
--- /dev/null
+++ b/common/libs/utils/socket2socket_proxy.h
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <functional>
+
+#include "common/libs/fs/shared_fd.h"
+
+namespace cuttlefish {
+// Executes a TCP proxy
+// Accept() is called on the server in a loop, for every client connection a
+// target connection is created through the conn_factory callback and data is
+// forwarded between the two connections.
+// This function is meant to execute forever, but will return if the server is
+// closed in another thread. It's recommended the caller disables the default
+// behavior for SIGPIPE before calling this function, otherwise it runs the risk
+// or crashing the process when a connection breaks.
+void Proxy(SharedFD server, std::function<SharedFD()> conn_factory);
+} // namespace cuttlefish
diff --git a/common/libs/utils/subprocess.cpp b/common/libs/utils/subprocess.cpp
index 7c05219..16a7989 100644
--- a/common/libs/utils/subprocess.cpp
+++ b/common/libs/utils/subprocess.cpp
@@ -16,7 +16,7 @@
#include "common/libs/utils/subprocess.h"
-#include <errno.h>
+#include <fcntl.h>
#include <signal.h>
#include <stdlib.h>
#include <sys/prctl.h>
@@ -24,13 +24,25 @@
#include <sys/wait.h>
#include <unistd.h>
+#include <cerrno>
+#include <cstring>
#include <map>
+#include <memory>
+#include <ostream>
#include <set>
+#include <sstream>
+#include <string>
#include <thread>
+#include <utility>
+#include <vector>
#include <android-base/logging.h>
+#include <android-base/strings.h>
#include "common/libs/fs/shared_buf.h"
+#include "common/libs/utils/files.h"
+
+extern char** environ;
namespace cuttlefish {
namespace {
@@ -74,14 +86,35 @@
ret.push_back(NULL);
return ret;
}
-
-void UnsetEnvironment(const std::unordered_set<std::string>& unenv) {
- for (auto it = unenv.cbegin(); it != unenv.cend(); ++it) {
- unsetenv(it->c_str());
- }
-}
} // namespace
+SubprocessOptions& SubprocessOptions::Verbose(bool verbose) & {
+ verbose_ = verbose;
+ return *this;
+}
+SubprocessOptions SubprocessOptions::Verbose(bool verbose) && {
+ verbose_ = verbose;
+ return *this;
+}
+
+SubprocessOptions& SubprocessOptions::ExitWithParent(bool v) & {
+ exit_with_parent_ = v;
+ return *this;
+}
+SubprocessOptions SubprocessOptions::ExitWithParent(bool v) && {
+ exit_with_parent_ = v;
+ return *this;
+}
+
+SubprocessOptions& SubprocessOptions::InGroup(bool in_group) & {
+ in_group_ = in_group;
+ return *this;
+}
+SubprocessOptions SubprocessOptions::InGroup(bool in_group) && {
+ in_group_ = in_group;
+ return *this;
+}
+
Subprocess::Subprocess(Subprocess&& subprocess)
: pid_(subprocess.pid_),
started_(subprocess.started_),
@@ -110,7 +143,7 @@
}
int wstatus = 0;
auto pid = pid_; // Wait will set pid_ to -1 after waiting
- auto wait_ret = Wait(&wstatus, 0);
+ auto wait_ret = waitpid(pid, &wstatus, 0);
if (wait_ret < 0) {
auto error = errno;
LOG(ERROR) << "Error on call to waitpid: " << strerror(error);
@@ -120,7 +153,7 @@
if (WIFEXITED(wstatus)) {
retval = WEXITSTATUS(wstatus);
if (retval) {
- LOG(ERROR) << "Subprocess " << pid
+ LOG(DEBUG) << "Subprocess " << pid
<< " exited with error code: " << retval;
}
} else if (WIFSIGNALED(wstatus)) {
@@ -130,20 +163,26 @@
}
return retval;
}
-pid_t Subprocess::Wait(int* wstatus, int options) {
+int Subprocess::Wait(siginfo_t* infop, int options) {
if (pid_ < 0) {
LOG(ERROR)
<< "Attempt to wait on invalid pid(has it been waited on already?): "
<< pid_;
return -1;
}
- auto retval = waitpid(pid_, wstatus, options);
+ *infop = {};
+ auto retval = waitid(P_PID, pid_, infop, options);
// We don't want to wait twice for the same process
- pid_ = -1;
+ bool exited = infop->si_code == CLD_EXITED || infop->si_code == CLD_DUMPED ||
+ infop->si_code == CLD_DUMPED;
+ bool reaped = !(options & WNOWAIT);
+ if (exited && reaped) {
+ pid_ = -1;
+ }
return retval;
}
-bool KillSubprocess(Subprocess* subprocess) {
+StopperResult KillSubprocess(Subprocess* subprocess) {
auto pid = subprocess->pid();
if (pid > 0) {
auto pgid = getpgid(pid);
@@ -155,13 +194,23 @@
// to the process and not a (non-existent) group
}
bool is_group_head = pid == pgid;
- if (is_group_head) {
- return killpg(pid, SIGKILL) == 0;
- } else {
- return kill(pid, SIGKILL) == 0;
+ auto kill_ret = (is_group_head ? killpg : kill)(pid, SIGKILL);
+ if (kill_ret == 0) {
+ return StopperResult::kStopSuccess;
}
+ auto kill_cmd = is_group_head ? "killpg(" : "kill(";
+ PLOG(ERROR) << kill_cmd << pid << ", SIGKILL) failed: ";
+ return StopperResult::kStopFailure;
}
- return true;
+ return StopperResult::kStopSuccess;
+}
+
+Command::Command(const std::string& executable, SubprocessStopper stopper)
+ : subprocess_stopper_(stopper) {
+ for (char** env = environ; *env; env++) {
+ env_.emplace_back(*env);
+ }
+ command_.push_back(executable);
}
Command::~Command() {
@@ -175,45 +224,68 @@
}
}
-bool Command::BuildParameter(std::stringstream* stream, SharedFD shared_fd) {
+void Command::BuildParameter(std::stringstream* stream, SharedFD shared_fd) {
int fd;
if (inherited_fds_.count(shared_fd)) {
fd = inherited_fds_[shared_fd];
} else {
fd = shared_fd->Fcntl(F_DUPFD_CLOEXEC, 3);
- if (fd < 0) {
- LOG(ERROR) << "Could not acquire a new file descriptor: " << shared_fd->StrError();
- return false;
- }
+ CHECK(fd >= 0) << "Could not acquire a new file descriptor: "
+ << shared_fd->StrError();
inherited_fds_[shared_fd] = fd;
}
*stream << fd;
- return true;
}
-bool Command::RedirectStdIO(Subprocess::StdIOChannel channel,
- SharedFD shared_fd) {
- if (!shared_fd->IsOpen()) {
- return false;
- }
- if (redirects_.count(channel)) {
- LOG(ERROR) << "Attempted multiple redirections of fd: "
- << static_cast<int>(channel);
- return false;
- }
+Command& Command::RedirectStdIO(Subprocess::StdIOChannel channel,
+ SharedFD shared_fd) & {
+ CHECK(shared_fd->IsOpen());
+ CHECK(redirects_.count(channel) == 0)
+ << "Attempted multiple redirections of fd: " << static_cast<int>(channel);
auto dup_fd = shared_fd->Fcntl(F_DUPFD_CLOEXEC, 3);
- if (dup_fd < 0) {
- LOG(ERROR) << "Could not acquire a new file descriptor: " << shared_fd->StrError();
- return false;
- }
+ CHECK(dup_fd >= 0) << "Could not acquire a new file descriptor: "
+ << shared_fd->StrError();
redirects_[channel] = dup_fd;
- return true;
+ return *this;
}
-bool Command::RedirectStdIO(Subprocess::StdIOChannel subprocess_channel,
- Subprocess::StdIOChannel parent_channel) {
+Command Command::RedirectStdIO(Subprocess::StdIOChannel channel,
+ SharedFD shared_fd) && {
+ RedirectStdIO(channel, shared_fd);
+ return std::move(*this);
+}
+Command& Command::RedirectStdIO(Subprocess::StdIOChannel subprocess_channel,
+ Subprocess::StdIOChannel parent_channel) & {
return RedirectStdIO(subprocess_channel,
SharedFD::Dup(static_cast<int>(parent_channel)));
}
+Command Command::RedirectStdIO(Subprocess::StdIOChannel subprocess_channel,
+ Subprocess::StdIOChannel parent_channel) && {
+ RedirectStdIO(subprocess_channel, parent_channel);
+ return std::move(*this);
+}
+
+Command& Command::SetWorkingDirectory(std::string path) & {
+ auto fd = SharedFD::Open(path, O_RDONLY | O_PATH | O_DIRECTORY);
+ CHECK(fd->IsOpen()) << "Could not open \"" << path
+ << "\" dir fd: " << fd->StrError();
+ return SetWorkingDirectory(fd);
+}
+Command Command::SetWorkingDirectory(std::string path) && {
+ auto fd = SharedFD::Open(path, O_RDONLY | O_PATH | O_DIRECTORY);
+ CHECK(fd->IsOpen()) << "Could not open \"" << path
+ << "\" dir fd: " << fd->StrError();
+ return std::move(SetWorkingDirectory(fd));
+}
+Command& Command::SetWorkingDirectory(SharedFD dirfd) & {
+ CHECK(dirfd->IsOpen()) << "Dir fd invalid: " << dirfd->StrError();
+ working_directory_ = dirfd;
+ return *this;
+}
+Command Command::SetWorkingDirectory(SharedFD dirfd) && {
+ CHECK(dirfd->IsOpen()) << "Dir fd invalid: " << dirfd->StrError();
+ working_directory_ = dirfd;
+ return std::move(*this);
+}
Subprocess Command::Start(SubprocessOptions options) const {
auto cmd = ToCharPointers(command_);
@@ -242,18 +314,15 @@
LOG(ERROR) << "fcntl failed: " << strerror(error_num);
}
}
- int rval;
- // If use_parent_env_ is false, the current process's environment is used as
- // the environment of the child process. To force an empty emvironment for
- // the child process pass the address of a pointer to NULL
- if (use_parent_env_) {
- UnsetEnvironment(unenv_);
- rval = execvp(cmd[0], const_cast<char* const*>(cmd.data()));
- } else {
- auto envp = ToCharPointers(env_);
- rval = execvpe(cmd[0], const_cast<char* const*>(cmd.data()),
- const_cast<char* const*>(envp.data()));
+ if (working_directory_->IsOpen()) {
+ if (SharedFD::Fchdir(working_directory_) != 0) {
+ LOG(ERROR) << "Fchdir failed: " << working_directory_->StrError();
+ }
}
+ int rval;
+ auto envp = ToCharPointers(env_);
+ rval = execvpe(cmd[0], const_cast<char* const*>(cmd.data()),
+ const_cast<char* const*>(envp.data()));
// No need for an if: if exec worked it wouldn't have returned
LOG(ERROR) << "exec of " << cmd[0] << " failed (" << strerror(errno)
<< ")";
@@ -276,6 +345,20 @@
return Subprocess(pid, subprocess_stopper_);
}
+std::string Command::AsBashScript(
+ const std::string& redirected_stdio_path) const {
+ CHECK(inherited_fds_.empty())
+ << "Bash wrapper will not have inheritied file descriptors.";
+ CHECK(redirects_.empty()) << "Bash wrapper will not have redirected stdio.";
+
+ std::string contents =
+ "#!/bin/bash\n\n" + android::base::Join(command_, " \\\n");
+ if (!redirected_stdio_path.empty()) {
+ contents += " &> " + AbsolutePath(redirected_stdio_path);
+ }
+ return contents;
+}
+
// A class that waits for threads to exit in its destructor.
class ThreadJoiner {
std::vector<std::thread*> threads_;
@@ -290,8 +373,8 @@
}
};
-int RunWithManagedStdio(Command&& cmd_tmp, const std::string* stdin,
- std::string* stdout, std::string* stderr,
+int RunWithManagedStdio(Command&& cmd_tmp, const std::string* stdin_str,
+ std::string* stdout_str, std::string* stderr_str,
SubprocessOptions options) {
/*
* The order of these declarations is necessary for safety. If the function
@@ -308,60 +391,48 @@
ThreadJoiner thread_joiner({&stdin_thread, &stdout_thread, &stderr_thread});
Command cmd = std::move(cmd_tmp);
bool io_error = false;
- if (stdin != nullptr) {
+ if (stdin_str != nullptr) {
SharedFD pipe_read, pipe_write;
if (!SharedFD::Pipe(&pipe_read, &pipe_write)) {
LOG(ERROR) << "Could not create a pipe to write the stdin of \""
<< cmd.GetShortName() << "\"";
return -1;
}
- if (!cmd.RedirectStdIO(Subprocess::StdIOChannel::kStdIn, pipe_read)) {
- LOG(ERROR) << "Could not set stdout of \"" << cmd.GetShortName()
- << "\", was already set.";
- return -1;
- }
- stdin_thread = std::thread([pipe_write, stdin, &io_error]() {
- int written = WriteAll(pipe_write, *stdin);
+ cmd.RedirectStdIO(Subprocess::StdIOChannel::kStdIn, pipe_read);
+ stdin_thread = std::thread([pipe_write, stdin_str, &io_error]() {
+ int written = WriteAll(pipe_write, *stdin_str);
if (written < 0) {
io_error = true;
LOG(ERROR) << "Error in writing stdin to process";
}
});
}
- if (stdout != nullptr) {
+ if (stdout_str != nullptr) {
SharedFD pipe_read, pipe_write;
if (!SharedFD::Pipe(&pipe_read, &pipe_write)) {
LOG(ERROR) << "Could not create a pipe to read the stdout of \""
<< cmd.GetShortName() << "\"";
return -1;
}
- if (!cmd.RedirectStdIO(Subprocess::StdIOChannel::kStdOut, pipe_write)) {
- LOG(ERROR) << "Could not set stdout of \"" << cmd.GetShortName()
- << "\", was already set.";
- return -1;
- }
- stdout_thread = std::thread([pipe_read, stdout, &io_error]() {
- int read = ReadAll(pipe_read, stdout);
+ cmd.RedirectStdIO(Subprocess::StdIOChannel::kStdOut, pipe_write);
+ stdout_thread = std::thread([pipe_read, stdout_str, &io_error]() {
+ int read = ReadAll(pipe_read, stdout_str);
if (read < 0) {
io_error = true;
LOG(ERROR) << "Error in reading stdout from process";
}
});
}
- if (stderr != nullptr) {
+ if (stderr_str != nullptr) {
SharedFD pipe_read, pipe_write;
if (!SharedFD::Pipe(&pipe_read, &pipe_write)) {
LOG(ERROR) << "Could not create a pipe to read the stderr of \""
<< cmd.GetShortName() << "\"";
return -1;
}
- if (!cmd.RedirectStdIO(Subprocess::StdIOChannel::kStdErr, pipe_write)) {
- LOG(ERROR) << "Could not set stderr of \"" << cmd.GetShortName()
- << "\", was already set.";
- return -1;
- }
- stderr_thread = std::thread([pipe_read, stderr, &io_error]() {
- int read = ReadAll(pipe_read, stderr);
+ cmd.RedirectStdIO(Subprocess::StdIOChannel::kStdErr, pipe_write);
+ stderr_thread = std::thread([pipe_read, stderr_str, &io_error]() {
+ int read = ReadAll(pipe_read, stderr_str);
if (read < 0) {
io_error = true;
LOG(ERROR) << "Error in reading stderr from process";
@@ -379,12 +450,8 @@
// This is necessary to close the write end of the pipe.
Command forceDelete = std::move(cmd);
}
- int wstatus;
- subprocess.Wait(&wstatus, 0);
- if (WIFSIGNALED(wstatus)) {
- LOG(ERROR) << "Command was interrupted by a signal: " << WTERMSIG(wstatus);
- return -1;
- }
+
+ int code = subprocess.Wait();
{
auto join_threads = std::move(thread_joiner);
}
@@ -392,7 +459,7 @@
LOG(ERROR) << "IO error communicating with " << cmd_short_name;
return -1;
}
- return WEXITSTATUS(wstatus);
+ return code;
}
int execute(const std::vector<std::string>& command,
diff --git a/common/libs/utils/subprocess.h b/common/libs/utils/subprocess.h
index 777a026..ff1af5b 100644
--- a/common/libs/utils/subprocess.h
+++ b/common/libs/utils/subprocess.h
@@ -16,25 +16,35 @@
#pragma once
#include <sys/types.h>
-
-#include <functional>
-#include <map>
-#include <sstream>
-#include <string>
-#include <unordered_set>
-#include <vector>
+#include <sys/wait.h>
#include <android-base/logging.h>
+#include <android-base/strings.h>
-#include <common/libs/fs/shared_fd.h>
+#include <cstdio>
+#include <cstring>
+#include <functional>
+#include <map>
+#include <ostream>
+#include <sstream>
+#include <string>
+#include <utility>
+#include <vector>
+
+#include "common/libs/fs/shared_fd.h"
namespace cuttlefish {
-class Command;
+
+enum class StopperResult {
+ kStopFailure, /* Failed to stop the subprocess. */
+ kStopCrash, /* Attempted to stop the subprocess cleanly, but that failed. */
+ kStopSuccess, /* The subprocess exited in the expected way. */
+};
+
class Subprocess;
-class SubprocessOptions;
-using SubprocessStopper = std::function<bool(Subprocess*)>;
+using SubprocessStopper = std::function<StopperResult(Subprocess*)>;
// Kills a process by sending it the SIGKILL signal.
-bool KillSubprocess(Subprocess* subprocess);
+StopperResult KillSubprocess(Subprocess* subprocess);
// Keeps track of a running (sub)process. Allows to wait for its completion.
// It's an error to wait twice for the same subprocess.
@@ -58,14 +68,14 @@
// Waits for the subprocess to complete. Returns zero if completed
// successfully, non-zero otherwise.
int Wait();
- // Same as waitpid(2)
- pid_t Wait(int* wstatus, int options);
+ // Same as waitid(2)
+ int Wait(siginfo_t* infop, int options);
// Whether the command started successfully. It only says whether the call to
// fork() succeeded or not, it says nothing about exec or successful
// completion of the command, that's what Wait is for.
bool Started() const { return started_; }
pid_t pid() const { return pid_; }
- bool Stop() { return stopper_(this); }
+ StopperResult Stop() { return stopper_(this); }
private:
// Copy is disabled to avoid waiting twice for the same pid (the first wait
@@ -79,26 +89,26 @@
};
class SubprocessOptions {
- bool verbose_;
- bool exit_with_parent_;
- bool in_group_;
-public:
- SubprocessOptions() : verbose_(true), exit_with_parent_(true) {}
+ public:
+ SubprocessOptions()
+ : verbose_(true), exit_with_parent_(true), in_group_(false) {}
- void Verbose(bool verbose) {
- verbose_ = verbose;
- }
- void ExitWithParent(bool exit_with_parent) {
- exit_with_parent_ = exit_with_parent;
- }
+ SubprocessOptions& Verbose(bool verbose) &;
+ SubprocessOptions Verbose(bool verbose) &&;
+ SubprocessOptions& ExitWithParent(bool exit_with_parent) &;
+ SubprocessOptions ExitWithParent(bool exit_with_parent) &&;
// The subprocess runs as head of its own process group.
- void InGroup(bool in_group) {
- in_group_ = in_group;
- }
+ SubprocessOptions& InGroup(bool in_group) &;
+ SubprocessOptions InGroup(bool in_group) &&;
bool Verbose() const { return verbose_; }
bool ExitWithParent() const { return exit_with_parent_; }
bool InGroup() const { return in_group_; }
+
+ private:
+ bool verbose_;
+ bool exit_with_parent_;
+ bool in_group_;
};
// An executable command. Multiple subprocesses can be started from the same
@@ -108,15 +118,15 @@
private:
template <typename T>
// For every type other than SharedFD (for which there is a specialisation)
- bool BuildParameter(std::stringstream* stream, T t) {
+ void BuildParameter(std::stringstream* stream, T t) {
*stream << t;
- return true;
}
// Special treatment for SharedFD
- bool BuildParameter(std::stringstream* stream, SharedFD shared_fd);
+ void BuildParameter(std::stringstream* stream, SharedFD shared_fd);
template <typename T, typename... Args>
- bool BuildParameter(std::stringstream* stream, T t, Args... args) {
- return BuildParameter(stream, t) && BuildParameter(stream, args...);
+ void BuildParameter(std::stringstream* stream, T t, Args... args) {
+ BuildParameter(stream, t);
+ BuildParameter(stream, args...);
}
public:
@@ -124,10 +134,7 @@
// optional subprocess stopper. When not provided, stopper defaults to sending
// SIGKILL to the subprocess.
Command(const std::string& executable,
- SubprocessStopper stopper = KillSubprocess)
- : subprocess_stopper_(stopper) {
- command_.push_back(executable);
- }
+ SubprocessStopper stopper = KillSubprocess);
Command(Command&&) = default;
// The default copy constructor is unsafe because it would mean multiple
// closing of the inherited file descriptors. If needed it can be implemented
@@ -136,18 +143,72 @@
Command& operator=(const Command&) = delete;
~Command();
- // Specify the environment for the subprocesses to be started. By default
- // subprocesses inherit the parent's environment.
- void SetEnvironment(const std::vector<std::string>& env) {
- use_parent_env_ = false;
- env_ = env;
+ const std::string& Executable() const { return command_[0]; }
+
+ Command& SetExecutable(const std::string& executable) & {
+ command_[0] = executable;
+ return *this;
+ }
+ Command SetExecutable(const std::string& executable) && {
+ SetExecutable(executable);
+ return std::move(*this);
}
- // Specify environment variables to be unset from the parent's environment
- // for the subprocesses to be started.
- void UnsetFromEnvironment(const std::vector<std::string>& env) {
- use_parent_env_ = true;
- std::copy(env.cbegin(), env.cend(), std::inserter(unenv_, unenv_.end()));
+ Command& SetStopper(SubprocessStopper stopper) & {
+ subprocess_stopper_ = stopper;
+ return *this;
+ }
+ Command SetStopper(SubprocessStopper stopper) && {
+ SetStopper(stopper);
+ return std::move(*this);
+ }
+
+ // Specify the environment for the subprocesses to be started. By default
+ // subprocesses inherit the parent's environment.
+ Command& SetEnvironment(const std::vector<std::string>& env) & {
+ env_ = env;
+ return *this;
+ }
+ Command SetEnvironment(const std::vector<std::string>& env) && {
+ SetEnvironment(env);
+ return std::move(*this);
+ }
+
+ Command& AddEnvironmentVariable(const std::string& env_var,
+ const std::string& value) & {
+ return AddEnvironmentVariable(env_var + "=" + value);
+ }
+ Command AddEnvironmentVariable(const std::string& env_var,
+ const std::string& value) && {
+ AddEnvironmentVariable(env_var, value);
+ return std::move(*this);
+ }
+
+ Command& AddEnvironmentVariable(const std::string& env_var) & {
+ env_.push_back(env_var);
+ return *this;
+ }
+ Command AddEnvironmentVariable(const std::string& env_var) && {
+ AddEnvironmentVariable(env_var);
+ return std::move(*this);
+ }
+
+ // Specify an environment variable to be unset from the parent's
+ // environment for the subprocesses to be started.
+ Command& UnsetFromEnvironment(const std::string& env_var) & {
+ auto it = env_.begin();
+ while (it != env_.end()) {
+ if (android::base::StartsWith(*it, env_var + "=")) {
+ it = env_.erase(it);
+ } else {
+ ++it;
+ }
+ }
+ return *this;
+ }
+ Command UnsetFromEnvironment(const std::string& env_var) && {
+ UnsetFromEnvironment(env_var);
+ return std::move(*this);
}
// Adds a single parameter to the command. All arguments are concatenated into
@@ -156,34 +217,47 @@
// object is destroyed. To add multiple parameters to the command the function
// must be called multiple times, one per parameter.
template <typename... Args>
- bool AddParameter(Args... args) {
+ Command& AddParameter(Args... args) & {
std::stringstream ss;
- if (BuildParameter(&ss, args...)) {
- command_.push_back(ss.str());
- return true;
- }
- return false;
+ BuildParameter(&ss, args...);
+ command_.push_back(ss.str());
+ return *this;
+ }
+ template <typename... Args>
+ Command AddParameter(Args... args) && {
+ AddParameter(std::forward<Args>(args)...);
+ return std::move(*this);
}
// Similar to AddParameter, except the args are appended to the last (most
// recently-added) parameter in the command.
template <typename... Args>
- bool AppendToLastParameter(Args... args) {
- if (command_.empty()) {
- LOG(ERROR) << "There is no parameter to append to.";
- return false;
- }
+ Command& AppendToLastParameter(Args... args) & {
+ CHECK(!command_.empty()) << "There is no parameter to append to.";
std::stringstream ss;
- if (BuildParameter(&ss, args...)) {
- command_[command_.size()-1] += ss.str();
- return true;
- }
- return false;
+ BuildParameter(&ss, args...);
+ command_[command_.size() - 1] += ss.str();
+ return *this;
+ }
+ template <typename... Args>
+ Command AppendToLastParameter(Args... args) && {
+ AppendToLastParameter(std::forward<Args>(args)...);
+ return std::move(*this);
}
// Redirects the standard IO of the command.
- bool RedirectStdIO(Subprocess::StdIOChannel channel, SharedFD shared_fd);
- bool RedirectStdIO(Subprocess::StdIOChannel subprocess_channel,
- Subprocess::StdIOChannel parent_channel);
+ Command& RedirectStdIO(Subprocess::StdIOChannel channel,
+ SharedFD shared_fd) &;
+ Command RedirectStdIO(Subprocess::StdIOChannel channel,
+ SharedFD shared_fd) &&;
+ Command& RedirectStdIO(Subprocess::StdIOChannel subprocess_channel,
+ Subprocess::StdIOChannel parent_channel) &;
+ Command RedirectStdIO(Subprocess::StdIOChannel subprocess_channel,
+ Subprocess::StdIOChannel parent_channel) &&;
+
+ Command& SetWorkingDirectory(std::string path) &;
+ Command SetWorkingDirectory(std::string path) &&;
+ Command& SetWorkingDirectory(SharedFD dirfd) &;
+ Command SetWorkingDirectory(SharedFD dirfd) &&;
// Starts execution of the command. This method can be called multiple times,
// effectively staring multiple (possibly concurrent) instances.
@@ -195,14 +269,19 @@
return command_[0];
}
+ // Generates the contents for a bash script that can be used to run this
+ // command. Note that this command must not require any file descriptors
+ // or stdio redirects as those would not be available when the bash script
+ // is run.
+ std::string AsBashScript(const std::string& redirected_stdio_path = "") const;
+
private:
std::vector<std::string> command_;
std::map<SharedFD, int> inherited_fds_{};
std::map<Subprocess::StdIOChannel, int> redirects_{};
- bool use_parent_env_ = true;
std::vector<std::string> env_{};
- std::unordered_set<std::string> unenv_{};
SubprocessStopper subprocess_stopper_;
+ SharedFD working_directory_;
};
/*
diff --git a/common/libs/utils/tcp_socket.cpp b/common/libs/utils/tcp_socket.cpp
index 3b56725..db96232 100644
--- a/common/libs/utils/tcp_socket.cpp
+++ b/common/libs/utils/tcp_socket.cpp
@@ -18,9 +18,12 @@
#include <netinet/in.h>
#include <sys/socket.h>
-#include <sys/types.h>
#include <cerrno>
+#include <cstring>
+#include <memory>
+#include <ostream>
+#include <string>
#include <android-base/logging.h>
diff --git a/common/libs/utils/tcp_socket.h b/common/libs/utils/tcp_socket.h
index 292decd..22827ed 100644
--- a/common/libs/utils/tcp_socket.h
+++ b/common/libs/utils/tcp_socket.h
@@ -23,13 +23,12 @@
#include <cstddef>
#include <cstdint>
#include <mutex>
+#include <string>
#include <vector>
namespace cuttlefish {
using Message = std::vector<std::uint8_t>;
-class ServerSocket;
-
// Recv and Send wait until all data has been received or sent.
// Send is thread safe in this regard, Recv is not.
class ClientSocket {
@@ -62,7 +61,7 @@
bool closed() const;
private:
- friend ServerSocket;
+ friend class ServerSocket;
explicit ClientSocket(SharedFD fd) : fd_(fd) {}
SharedFD fd_;
diff --git a/common/libs/utils/tee_logging.cpp b/common/libs/utils/tee_logging.cpp
index 66b46c1..8129b40 100644
--- a/common/libs/utils/tee_logging.cpp
+++ b/common/libs/utils/tee_logging.cpp
@@ -15,9 +15,22 @@
#include "tee_logging.h"
-#include <stdlib.h>
-#include <inttypes.h>
+#include <fcntl.h>
+#include <string.h>
+#include <unistd.h>
+#include <cinttypes>
+#include <cstring>
+#include <ctime>
+#include <memory>
+#include <ostream>
+#include <sstream>
+#include <string>
+#include <utility>
+#include <vector>
+
+#include <android-base/logging.h>
+#include <android-base/macros.h>
#include <android-base/stringprintf.h>
#include <android-base/strings.h>
#include <android-base/threads.h>
@@ -77,9 +90,9 @@
return GuessSeverity("CF_FILE_SEVERITY", android::base::DEBUG);
}
-TeeLogger::TeeLogger(const std::vector<SeverityTarget>& destinations)
- : destinations_(destinations) {
-}
+TeeLogger::TeeLogger(const std::vector<SeverityTarget>& destinations,
+ const std::string& prefix)
+ : destinations_(destinations), prefix_(prefix) {}
// Copied from system/libbase/logging_splitters.h
static std::pair<int, int> CountSizeAndNewLines(const char* message) {
@@ -175,15 +188,17 @@
unsigned int line,
const char* message) {
for (const auto& destination : destinations_) {
+ std::string msg_with_prefix = prefix_ + message;
std::string output_string;
if (destination.metadata_level == MetadataLevel::ONLY_MESSAGE) {
- output_string = message + std::string("\n");
+ output_string = msg_with_prefix + std::string("\n");
} else {
struct tm now;
time_t t = time(nullptr);
localtime_r(&t, &now);
- output_string = StderrOutputGenerator(now, getpid(), GetThreadId(),
- severity, tag, file, line, message);
+ output_string =
+ StderrOutputGenerator(now, getpid(), GetThreadId(), severity, tag,
+ file, line, msg_with_prefix.c_str());
}
if (severity >= destination.severity) {
if (destination.target->IsATTY()) {
@@ -213,16 +228,18 @@
return log_severities;
}
-TeeLogger LogToFiles(const std::vector<std::string>& files) {
- return TeeLogger(SeverityTargetsForFiles(files));
+TeeLogger LogToFiles(const std::vector<std::string>& files,
+ const std::string& prefix) {
+ return TeeLogger(SeverityTargetsForFiles(files), prefix);
}
-TeeLogger LogToStderrAndFiles(const std::vector<std::string>& files) {
+TeeLogger LogToStderrAndFiles(const std::vector<std::string>& files,
+ const std::string& prefix) {
std::vector<SeverityTarget> log_severities = SeverityTargetsForFiles(files);
log_severities.push_back(SeverityTarget{ConsoleSeverity(),
SharedFD::Dup(/* stderr */ 2),
MetadataLevel::ONLY_MESSAGE});
- return TeeLogger(log_severities);
+ return TeeLogger(log_severities, prefix);
}
} // namespace cuttlefish
diff --git a/common/libs/utils/tee_logging.h b/common/libs/utils/tee_logging.h
index 6306e62..ac3e70c 100644
--- a/common/libs/utils/tee_logging.h
+++ b/common/libs/utils/tee_logging.h
@@ -42,19 +42,21 @@
private:
std::vector<SeverityTarget> destinations_;
public:
- TeeLogger(const std::vector<SeverityTarget>& destinations);
- ~TeeLogger() = default;
+ TeeLogger(const std::vector<SeverityTarget>& destinations,
+ const std::string& log_prefix = "");
+ ~TeeLogger() = default;
- void operator()(
- android::base::LogId log_id,
- android::base::LogSeverity severity,
- const char* tag,
- const char* file,
- unsigned int line,
- const char* message);
+ void operator()(android::base::LogId log_id,
+ android::base::LogSeverity severity, const char* tag,
+ const char* file, unsigned int line, const char* message);
+
+private:
+ std::string prefix_;
};
-TeeLogger LogToFiles(const std::vector<std::string>& files);
-TeeLogger LogToStderrAndFiles(const std::vector<std::string>& files);
+TeeLogger LogToFiles(const std::vector<std::string>& files,
+ const std::string& log_prefix = "");
+TeeLogger LogToStderrAndFiles(const std::vector<std::string>& files,
+ const std::string& log_prefix = "");
} // namespace cuttlefish
diff --git a/common/libs/utils/unix_sockets.cpp b/common/libs/utils/unix_sockets.cpp
new file mode 100644
index 0000000..cd638c5
--- /dev/null
+++ b/common/libs/utils/unix_sockets.cpp
@@ -0,0 +1,283 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#include "common/libs/utils/unix_sockets.h"
+
+#include <fcntl.h>
+#include <sys/uio.h>
+#include <unistd.h>
+
+#include <cstring>
+#include <memory>
+#include <ostream>
+#include <utility>
+#include <vector>
+
+#include <android-base/logging.h>
+
+#include "common/libs/fs/shared_fd.h"
+#include "common/libs/utils/result.h"
+
+// This would use android::base::ReceiveFileDescriptors, but it silently drops
+// SCM_CREDENTIALS control messages.
+
+namespace cuttlefish {
+
+ControlMessage ControlMessage::FromRaw(const cmsghdr* cmsg) {
+ ControlMessage message;
+ message.data_ =
+ std::vector<char>((char*)cmsg, ((char*)cmsg) + cmsg->cmsg_len);
+ if (message.IsFileDescriptors()) {
+ size_t fdcount =
+ static_cast<size_t>(cmsg->cmsg_len - CMSG_LEN(0)) / sizeof(int);
+ for (int i = 0; i < fdcount; i++) {
+ // Use memcpy as CMSG_DATA may be unaligned
+ int fd = -1;
+ memcpy(&fd, CMSG_DATA(cmsg) + (i * sizeof(int)), sizeof(fd));
+ message.fds_.push_back(fd);
+ }
+ }
+ return message;
+}
+
+Result<ControlMessage> ControlMessage::FromFileDescriptors(
+ const std::vector<SharedFD>& fds) {
+ ControlMessage message;
+ message.data_.resize(CMSG_SPACE(fds.size() * sizeof(int)), 0);
+ message.Raw()->cmsg_len = CMSG_LEN(fds.size() * sizeof(int));
+ message.Raw()->cmsg_level = SOL_SOCKET;
+ message.Raw()->cmsg_type = SCM_RIGHTS;
+ for (int i = 0; i < fds.size(); i++) {
+ int fd_copy = fds[i]->Fcntl(F_DUPFD_CLOEXEC, 3);
+ CF_EXPECT(fd_copy >= 0, "Failed to duplicate fd: " << fds[i]->StrError());
+ message.fds_.push_back(fd_copy);
+ // Following the CMSG_DATA spec, use memcpy to avoid alignment issues.
+ memcpy(CMSG_DATA(message.Raw()) + (i * sizeof(int)), &fd_copy, sizeof(int));
+ }
+ return message;
+}
+
+ControlMessage ControlMessage::FromCredentials(const ucred& credentials) {
+ ControlMessage message;
+ message.data_.resize(CMSG_SPACE(sizeof(ucred)), 0);
+ message.Raw()->cmsg_len = CMSG_LEN(sizeof(ucred));
+ message.Raw()->cmsg_level = SOL_SOCKET;
+ message.Raw()->cmsg_type = SCM_CREDENTIALS;
+ // Following the CMSG_DATA spec, use memcpy to avoid alignment issues.
+ memcpy(CMSG_DATA(message.Raw()), &credentials, sizeof(credentials));
+ return message;
+}
+
+ControlMessage::ControlMessage(ControlMessage&& existing) {
+ // Enforce that the old ControlMessage is left empty, so it doesn't try to
+ // close any file descriptors. https://stackoverflow.com/a/17735913
+ data_ = std::move(existing.data_);
+ existing.data_.clear();
+ fds_ = std::move(existing.fds_);
+ existing.fds_.clear();
+}
+
+ControlMessage& ControlMessage::operator=(ControlMessage&& existing) {
+ // Enforce that the old ControlMessage is left empty, so it doesn't try to
+ // close any file descriptors. https://stackoverflow.com/a/17735913
+ data_ = std::move(existing.data_);
+ existing.data_.clear();
+ fds_ = std::move(existing.fds_);
+ existing.fds_.clear();
+ return *this;
+}
+
+ControlMessage::~ControlMessage() {
+ for (const auto& fd : fds_) {
+ if (close(fd) != 0) {
+ PLOG(ERROR) << "Failed to close fd " << fd
+ << ", may have leaked or closed prematurely";
+ }
+ }
+}
+
+cmsghdr* ControlMessage::Raw() {
+ return reinterpret_cast<cmsghdr*>(data_.data());
+}
+
+const cmsghdr* ControlMessage::Raw() const {
+ return reinterpret_cast<const cmsghdr*>(data_.data());
+}
+
+bool ControlMessage::IsCredentials() const {
+ bool right_level = Raw()->cmsg_level == SOL_SOCKET;
+ bool right_type = Raw()->cmsg_type == SCM_CREDENTIALS;
+ bool enough_data = Raw()->cmsg_len >= sizeof(cmsghdr) + sizeof(ucred);
+ return right_level && right_type && enough_data;
+}
+
+Result<ucred> ControlMessage::AsCredentials() const {
+ CF_EXPECT(IsCredentials(), "Control message does not hold a credential");
+ ucred credentials;
+ memcpy(&credentials, CMSG_DATA(Raw()), sizeof(ucred));
+ return credentials;
+}
+
+bool ControlMessage::IsFileDescriptors() const {
+ bool right_level = Raw()->cmsg_level == SOL_SOCKET;
+ bool right_type = Raw()->cmsg_type == SCM_RIGHTS;
+ return right_level && right_type;
+}
+
+Result<std::vector<SharedFD>> ControlMessage::AsSharedFDs() const {
+ CF_EXPECT(IsFileDescriptors(), "Message does not contain file descriptors");
+ size_t fdcount =
+ static_cast<size_t>(Raw()->cmsg_len - CMSG_LEN(0)) / sizeof(int);
+ std::vector<SharedFD> shared_fds;
+ for (int i = 0; i < fdcount; i++) {
+ // Use memcpy as CMSG_DATA may be unaligned
+ int fd = -1;
+ memcpy(&fd, CMSG_DATA(Raw()) + (i * sizeof(int)), sizeof(fd));
+ SharedFD shared_fd = SharedFD::Dup(fd);
+ CF_EXPECT(shared_fd->IsOpen(), "Could not dup FD " << fd);
+ shared_fds.push_back(shared_fd);
+ }
+ return shared_fds;
+}
+
+bool UnixSocketMessage::HasFileDescriptors() {
+ for (const auto& control_message : control) {
+ if (control_message.IsFileDescriptors()) {
+ return true;
+ }
+ }
+ return false;
+}
+Result<std::vector<SharedFD>> UnixSocketMessage::FileDescriptors() {
+ std::vector<SharedFD> fds;
+ for (const auto& control_message : control) {
+ if (control_message.IsFileDescriptors()) {
+ auto additional_fds = CF_EXPECT(control_message.AsSharedFDs());
+ fds.insert(fds.end(), additional_fds.begin(), additional_fds.end());
+ }
+ }
+ return fds;
+}
+bool UnixSocketMessage::HasCredentials() {
+ for (const auto& control_message : control) {
+ if (control_message.IsCredentials()) {
+ return true;
+ }
+ }
+ return false;
+}
+Result<ucred> UnixSocketMessage::Credentials() {
+ std::vector<ucred> credentials;
+ for (const auto& control_message : control) {
+ if (control_message.IsCredentials()) {
+ auto creds = CF_EXPECT(control_message.AsCredentials(),
+ "Message claims to have credentials but does not");
+ credentials.push_back(creds);
+ }
+ }
+ if (credentials.size() == 0) {
+ return CF_ERR("No credentials present");
+ } else if (credentials.size() == 1) {
+ return credentials[0];
+ } else {
+ return CF_ERR("Excepted 1 credential, received " << credentials.size());
+ }
+}
+
+UnixMessageSocket::UnixMessageSocket(SharedFD socket) : socket_(socket) {
+ socklen_t ln = sizeof(max_message_size_);
+ CHECK(socket->GetSockOpt(SOL_SOCKET, SO_SNDBUF, &max_message_size_, &ln) == 0)
+ << "error: can't retrieve socket max message size: "
+ << socket->StrError();
+}
+
+Result<void> UnixMessageSocket::EnableCredentials(bool enable) {
+ int flag = enable ? 1 : 0;
+ if (socket_->SetSockOpt(SOL_SOCKET, SO_PASSCRED, &flag, sizeof(flag)) != 0) {
+ return CF_ERR("Could not set credential status to " << enable << ": "
+ << socket_->StrError());
+ }
+ return {};
+}
+
+Result<void> UnixMessageSocket::WriteMessage(const UnixSocketMessage& message) {
+ auto control_size = 0;
+ for (const auto& control : message.control) {
+ control_size += control.data_.size();
+ }
+ std::vector<char> message_control(control_size, 0);
+ msghdr message_header{};
+ message_header.msg_control = message_control.data();
+ message_header.msg_controllen = message_control.size();
+ auto cmsg = CMSG_FIRSTHDR(&message_header);
+ for (const ControlMessage& control : message.control) {
+ CF_EXPECT(cmsg != nullptr,
+ "Control messages did not fit in control buffer");
+ /* size() should match CMSG_SPACE */
+ memcpy(cmsg, control.data_.data(), control.data_.size());
+ cmsg = CMSG_NXTHDR(&message_header, cmsg);
+ }
+
+ iovec message_iovec;
+ message_iovec.iov_base = (void*)message.data.data();
+ message_iovec.iov_len = message.data.size();
+ message_header.msg_name = nullptr;
+ message_header.msg_namelen = 0;
+ message_header.msg_iov = &message_iovec;
+ message_header.msg_iovlen = 1;
+ message_header.msg_flags = 0;
+
+ auto bytes_sent = socket_->SendMsg(&message_header, MSG_NOSIGNAL);
+ CF_EXPECT(bytes_sent >= 0, "Failed to send message: " << socket_->StrError());
+ CF_EXPECT(bytes_sent == message.data.size(),
+ "Failed to send entire message. Sent "
+ << bytes_sent << ", excepted to send " << message.data.size());
+ return {};
+}
+
+Result<UnixSocketMessage> UnixMessageSocket::ReadMessage() {
+ msghdr message_header{};
+ std::vector<char> message_control(max_message_size_, 0);
+ message_header.msg_control = message_control.data();
+ message_header.msg_controllen = message_control.size();
+ std::vector<char> message_data(max_message_size_, 0);
+ iovec message_iovec;
+ message_iovec.iov_base = message_data.data();
+ message_iovec.iov_len = message_data.size();
+ message_header.msg_iov = &message_iovec;
+ message_header.msg_iovlen = 1;
+ message_header.msg_name = nullptr;
+ message_header.msg_namelen = 0;
+ message_header.msg_flags = 0;
+
+ auto bytes_read = socket_->RecvMsg(&message_header, MSG_CMSG_CLOEXEC);
+ CF_EXPECT(bytes_read >= 0, "Read error: " << socket_->StrError());
+ CF_EXPECT(!(message_header.msg_flags & MSG_TRUNC),
+ "Message was truncated on read");
+ CF_EXPECT(!(message_header.msg_flags & MSG_CTRUNC),
+ "Message control data was truncated on read");
+ CF_EXPECT(!(message_header.msg_flags & MSG_ERRQUEUE), "Error queue error");
+ UnixSocketMessage managed_message;
+ for (auto cmsg = CMSG_FIRSTHDR(&message_header); cmsg != nullptr;
+ cmsg = CMSG_NXTHDR(&message_header, cmsg)) {
+ managed_message.control.emplace_back(ControlMessage::FromRaw(cmsg));
+ }
+ message_data.resize(bytes_read);
+ managed_message.data = std::move(message_data);
+
+ return managed_message;
+}
+
+} // namespace cuttlefish
diff --git a/common/libs/utils/unix_sockets.h b/common/libs/utils/unix_sockets.h
new file mode 100644
index 0000000..4c9881a
--- /dev/null
+++ b/common/libs/utils/unix_sockets.h
@@ -0,0 +1,80 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#pragma once
+
+#include <sys/socket.h>
+
+#include <cstdint>
+#include <vector>
+
+#include "common/libs/fs/shared_fd.h"
+#include "common/libs/utils/result.h"
+
+namespace cuttlefish {
+
+struct ControlMessage {
+ public:
+ static ControlMessage FromRaw(const cmsghdr*);
+ static Result<ControlMessage> FromFileDescriptors(
+ const std::vector<SharedFD>&);
+ static ControlMessage FromCredentials(const ucred&);
+ ControlMessage(const ControlMessage&) = delete;
+ ControlMessage(ControlMessage&&);
+ ~ControlMessage();
+ ControlMessage& operator=(const ControlMessage&) = delete;
+ ControlMessage& operator=(ControlMessage&&);
+
+ const cmsghdr* Raw() const;
+
+ bool IsCredentials() const;
+ Result<ucred> AsCredentials() const;
+
+ bool IsFileDescriptors() const;
+ Result<std::vector<SharedFD>> AsSharedFDs() const;
+
+ private:
+ friend class UnixMessageSocket;
+ ControlMessage() = default;
+ cmsghdr* Raw();
+
+ std::vector<char> data_;
+ std::vector<int> fds_;
+};
+
+struct UnixSocketMessage {
+ std::vector<char> data;
+ std::vector<ControlMessage> control;
+
+ bool HasFileDescriptors();
+ Result<std::vector<SharedFD>> FileDescriptors();
+ bool HasCredentials();
+ Result<ucred> Credentials();
+};
+
+class UnixMessageSocket {
+ public:
+ UnixMessageSocket(SharedFD);
+ [[nodiscard]] Result<void> WriteMessage(const UnixSocketMessage&);
+ Result<UnixSocketMessage> ReadMessage();
+
+ [[nodiscard]] Result<void> EnableCredentials(bool);
+
+ private:
+ SharedFD socket_;
+ std::uint32_t max_message_size_;
+};
+
+} // namespace cuttlefish
diff --git a/common/libs/utils/unix_sockets_test.cpp b/common/libs/utils/unix_sockets_test.cpp
new file mode 100644
index 0000000..4475064
--- /dev/null
+++ b/common/libs/utils/unix_sockets_test.cpp
@@ -0,0 +1,197 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "common/libs/utils/unix_sockets.h"
+
+#include <android-base/logging.h>
+#include <android-base/result.h>
+#include <gtest/gtest.h>
+
+#include "common/libs/fs/shared_buf.h"
+#include "common/libs/fs/shared_fd.h"
+
+namespace cuttlefish {
+
+SharedFD CreateMemFDWithData(const std::string& data) {
+ auto memfd = SharedFD::MemfdCreate("");
+ CHECK(WriteAll(memfd, data) == data.size()) << memfd->StrError();
+ CHECK(memfd->LSeek(0, SEEK_SET) == 0);
+ return memfd;
+}
+
+std::string ReadAllFDData(SharedFD fd) {
+ std::string data;
+ CHECK(ReadAll(fd, &data) > 0) << fd->StrError();
+ return data;
+}
+
+TEST(UnixSocketMessage, ExtractFileDescriptors) {
+ auto memfd1 = CreateMemFDWithData("abc");
+ auto memfd2 = CreateMemFDWithData("def");
+
+ UnixSocketMessage message;
+ auto control1 = ControlMessage::FromFileDescriptors({memfd1});
+ ASSERT_TRUE(control1.ok()) << control1.error();
+ message.control.emplace_back(std::move(*control1));
+ auto control2 = ControlMessage::FromFileDescriptors({memfd2});
+ ASSERT_TRUE(control2.ok()) << control2.error();
+ message.control.emplace_back(std::move(*control2));
+
+ ASSERT_TRUE(message.HasFileDescriptors());
+ auto fds = message.FileDescriptors();
+ ASSERT_TRUE(fds.ok());
+ ASSERT_EQ("abc", ReadAllFDData((*fds)[0]));
+ ASSERT_EQ("def", ReadAllFDData((*fds)[1]));
+}
+
+std::pair<UnixMessageSocket, UnixMessageSocket> UnixMessageSocketPair() {
+ SharedFD sock1, sock2;
+ CHECK(SharedFD::SocketPair(AF_UNIX, SOCK_SEQPACKET, 0, &sock1, &sock2));
+ return {UnixMessageSocket(sock1), UnixMessageSocket(sock2)};
+}
+
+TEST(UnixMessageSocket, SendPlainMessage) {
+ auto [writer, reader] = UnixMessageSocketPair();
+ UnixSocketMessage message_in = {{1, 2, 3}, {}};
+ auto write_result = writer.WriteMessage(message_in);
+ ASSERT_TRUE(write_result.ok()) << write_result.error();
+
+ auto message_out = reader.ReadMessage();
+ ASSERT_TRUE(message_out.ok()) << message_out.error();
+ ASSERT_EQ(message_in.data, message_out->data);
+ ASSERT_EQ(0, message_out->control.size());
+}
+
+TEST(UnixMessageSocket, SendFileDescriptor) {
+ auto [writer, reader] = UnixMessageSocketPair();
+
+ UnixSocketMessage message_in = {{4, 5, 6}, {}};
+ auto control_in =
+ ControlMessage::FromFileDescriptors({CreateMemFDWithData("abc")});
+ ASSERT_TRUE(control_in.ok()) << control_in.error();
+ message_in.control.emplace_back(std::move(*control_in));
+ auto write_result = writer.WriteMessage(message_in);
+ ASSERT_TRUE(write_result.ok()) << write_result.error();
+
+ auto message_out = reader.ReadMessage();
+ ASSERT_TRUE(message_out.ok()) << message_out.error();
+ ASSERT_EQ(message_in.data, message_out->data);
+
+ ASSERT_EQ(1, message_out->control.size());
+ auto fds_out = message_out->control[0].AsSharedFDs();
+ ASSERT_TRUE(fds_out.ok()) << fds_out.error();
+ ASSERT_EQ(1, fds_out->size());
+ ASSERT_EQ("abc", ReadAllFDData((*fds_out)[0]));
+}
+
+TEST(UnixMessageSocket, SendTwoFileDescriptors) {
+ auto memfd1 = CreateMemFDWithData("abc");
+ auto memfd2 = CreateMemFDWithData("def");
+
+ auto [writer, reader] = UnixMessageSocketPair();
+ UnixSocketMessage message_in = {{7, 8, 9}, {}};
+ auto control_in = ControlMessage::FromFileDescriptors({memfd1, memfd2});
+ ASSERT_TRUE(control_in.ok()) << control_in.error();
+ message_in.control.emplace_back(std::move(*control_in));
+ auto write_result = writer.WriteMessage(message_in);
+ ASSERT_TRUE(write_result.ok()) << write_result.error();
+
+ auto message_out = reader.ReadMessage();
+ ASSERT_TRUE(message_out.ok()) << message_out.error();
+ ASSERT_EQ(message_in.data, message_out->data);
+
+ ASSERT_EQ(1, message_out->control.size());
+ auto fds_out = message_out->control[0].AsSharedFDs();
+ ASSERT_TRUE(fds_out.ok()) << fds_out.error();
+ ASSERT_EQ(2, fds_out->size());
+
+ ASSERT_EQ("abc", ReadAllFDData((*fds_out)[0]));
+ ASSERT_EQ("def", ReadAllFDData((*fds_out)[1]));
+}
+
+TEST(UnixMessageSocket, SendCredentials) {
+ auto [writer, reader] = UnixMessageSocketPair();
+ auto writer_creds_status = writer.EnableCredentials(true);
+ ASSERT_TRUE(writer_creds_status.ok()) << writer_creds_status.error();
+ auto reader_creds_status = reader.EnableCredentials(true);
+ ASSERT_TRUE(reader_creds_status.ok()) << reader_creds_status.error();
+
+ ucred credentials_in;
+ credentials_in.pid = getpid();
+ credentials_in.uid = getuid();
+ credentials_in.gid = getgid();
+ UnixSocketMessage message_in = {{1, 5, 9}, {}};
+ auto control_in = ControlMessage::FromCredentials(credentials_in);
+ message_in.control.emplace_back(std::move(control_in));
+ auto write_result = writer.WriteMessage(message_in);
+ ASSERT_TRUE(write_result.ok()) << write_result.error();
+
+ auto message_out = reader.ReadMessage();
+ ASSERT_TRUE(message_out.ok()) << message_out.error();
+ ASSERT_EQ(message_in.data, message_out->data);
+
+ ASSERT_EQ(1, message_out->control.size());
+ auto credentials_out = message_out->control[0].AsCredentials();
+ ASSERT_TRUE(credentials_out.ok()) << credentials_out.error();
+ ASSERT_EQ(credentials_in.pid, credentials_out->pid);
+ ASSERT_EQ(credentials_in.uid, credentials_out->uid);
+ ASSERT_EQ(credentials_in.gid, credentials_out->gid);
+}
+
+TEST(UnixMessageSocket, BadCredentialsBlocked) {
+ auto [writer, reader] = UnixMessageSocketPair();
+ auto writer_creds_status = writer.EnableCredentials(true);
+ ASSERT_TRUE(writer_creds_status.ok()) << writer_creds_status.error();
+ auto reader_creds_status = reader.EnableCredentials(true);
+ ASSERT_TRUE(reader_creds_status.ok()) << reader_creds_status.error();
+
+ ucred credentials_in;
+ // This assumes the test is running without root privileges
+ credentials_in.pid = getpid() + 1;
+ credentials_in.uid = getuid() + 1;
+ credentials_in.gid = getgid() + 1;
+
+ UnixSocketMessage message_in = {{2, 4, 6}, {}};
+ auto control_in = ControlMessage::FromCredentials(credentials_in);
+ message_in.control.emplace_back(std::move(control_in));
+ auto write_result = writer.WriteMessage(message_in);
+ ASSERT_FALSE(write_result.ok()) << write_result.error();
+}
+
+TEST(UnixMessageSocket, AutoCredentials) {
+ auto [writer, reader] = UnixMessageSocketPair();
+ auto writer_creds_status = writer.EnableCredentials(true);
+ ASSERT_TRUE(writer_creds_status.ok()) << writer_creds_status.error();
+ auto reader_creds_status = reader.EnableCredentials(true);
+ ASSERT_TRUE(reader_creds_status.ok()) << reader_creds_status.error();
+
+ UnixSocketMessage message_in = {{3, 6, 9}, {}};
+ auto write_result = writer.WriteMessage(message_in);
+ ASSERT_TRUE(write_result.ok()) << write_result.error();
+
+ auto message_out = reader.ReadMessage();
+ ASSERT_TRUE(message_out.ok()) << message_out.error();
+ ASSERT_EQ(message_in.data, message_out->data);
+
+ ASSERT_EQ(1, message_out->control.size());
+ auto credentials_out = message_out->control[0].AsCredentials();
+ ASSERT_TRUE(credentials_out.ok()) << credentials_out.error();
+ ASSERT_EQ(getpid(), credentials_out->pid);
+ ASSERT_EQ(getuid(), credentials_out->uid);
+ ASSERT_EQ(getgid(), credentials_out->gid);
+}
+
+} // namespace cuttlefish
diff --git a/common/libs/utils/users.cpp b/common/libs/utils/users.cpp
index 31a4a5e..a3699de 100644
--- a/common/libs/utils/users.cpp
+++ b/common/libs/utils/users.cpp
@@ -16,13 +16,14 @@
#include "common/libs/utils/users.h"
-#include <cerrno>
-#include <cstring>
-#include <sys/types.h>
-#include <unistd.h>
#include <grp.h>
+#include <unistd.h>
#include <algorithm>
+#include <cerrno>
+#include <cstring>
+#include <ostream>
+#include <string>
#include <vector>
#include <android-base/logging.h>
diff --git a/common/libs/utils/vsock_connection.cpp b/common/libs/utils/vsock_connection.cpp
index 6142b69..fbdb02b 100644
--- a/common/libs/utils/vsock_connection.cpp
+++ b/common/libs/utils/vsock_connection.cpp
@@ -16,11 +16,26 @@
#include "common/libs/utils/vsock_connection.h"
+#include <sys/socket.h>
+#include <sys/time.h>
+
+#include <functional>
+#include <future>
+#include <memory>
+#include <mutex>
+#include <new>
+#include <ostream>
+#include <string>
+#include <tuple>
+#include <utility>
+#include <vector>
+
+#include <android-base/logging.h>
+#include <json/json.h>
+
#include "common/libs/fs/shared_buf.h"
#include "common/libs/fs/shared_select.h"
-#include "android-base/logging.h"
-
namespace cuttlefish {
VsockConnection::~VsockConnection() { Disconnect(); }
diff --git a/common/libs/utils/vsock_connection.h b/common/libs/utils/vsock_connection.h
index 1a16b2f..c7a796a 100644
--- a/common/libs/utils/vsock_connection.h
+++ b/common/libs/utils/vsock_connection.h
@@ -15,11 +15,16 @@
*/
#pragma once
-#include <json/json.h>
+#include <cstddef>
+#include <cstdint>
#include <functional>
#include <future>
#include <mutex>
+#include <string>
#include <vector>
+
+#include <json/json.h>
+
#include "common/libs/fs/shared_fd.h"
namespace cuttlefish {
diff --git a/default-permissions.xml b/default-permissions.xml
index eb50a4d..48b0f17 100644
--- a/default-permissions.xml
+++ b/default-permissions.xml
@@ -54,7 +54,11 @@
<permission name="android.permission.READ_CALL_LOG" fixed="false"/>
<permission name="android.permission.WRITE_CALL_LOG" fixed="false"/>
<!-- Used to set up a Wi-Fi P2P network -->
+ <!-- TODO(b/231966826): Remove the location permission after Restore targets to T. -->
<permission name="android.permission.ACCESS_FINE_LOCATION" fixed="false"/>
+ <permission name="android.permission.NEARBY_WIFI_DEVICES" fixed="false"/>
+ <!-- Notifications -->
+ <permission name="android.permission.POST_NOTIFICATIONS" fixed="false"/>
</exception>
<exception
@@ -104,4 +108,29 @@
<permission name="android.permission.READ_EXTERNAL_STORAGE" fixed="false"/>
<permission name="android.permission.WRITE_EXTERNAL_STORAGE" fixed="false"/>
</exception>
+ <exception
+ package="com.google.android.deskclock">
+ <!-- Notifications -->
+ <permission name="android.permission.POST_NOTIFICATIONS" fixed="false"/>
+ </exception>
+ <exception
+ package="com.google.android.apps.tips">
+ <!-- Notifications -->
+ <permission name="android.permission.POST_NOTIFICATIONS" fixed="false"/>
+ </exception>
+ <exception
+ package="com.google.android.adservices">
+ <!-- Notifications -->
+ <permission name="android.permission.POST_NOTIFICATIONS" fixed="false"/>
+ </exception>
+ <exception
+ package="com.google.android.apps.mediashell">
+ <!-- Notifications -->
+ <permission name="android.permission.POST_NOTIFICATIONS" fixed="false"/>
+ </exception>
+ <exception
+ package="com.google.android.apps.pixelmigrate">
+ <!-- Notifications -->
+ <permission name="android.permission.POST_NOTIFICATIONS" fixed="false"/>
+ </exception>
</exceptions>
diff --git a/guest/commands/bt_vhci_forwarder/bt_vhci_forwarder.rc b/guest/commands/bt_vhci_forwarder/bt_vhci_forwarder.rc
new file mode 100644
index 0000000..8b7fb36
--- /dev/null
+++ b/guest/commands/bt_vhci_forwarder/bt_vhci_forwarder.rc
@@ -0,0 +1,7 @@
+on post-fs
+ start bt_vhci_forwarder
+
+service bt_vhci_forwarder /vendor/bin/bt_vhci_forwarder -virtio_console_dev=${vendor.ser.bt-uart}
+ user bluetooth
+ group bluetooth
+
diff --git a/guest/commands/bt_vhci_forwarder/main.cpp b/guest/commands/bt_vhci_forwarder/main.cpp
index d5c4622..2f1aab2 100644
--- a/guest/commands/bt_vhci_forwarder/main.cpp
+++ b/guest/commands/bt_vhci_forwarder/main.cpp
@@ -26,7 +26,7 @@
#include "android-base/logging.h"
-#include "model/devices/h4_packetizer.h"
+#include "model/hci/h4_packetizer.h"
// Copied from net/bluetooth/hci.h
#define HCI_ACLDATA_PKT 0x02
@@ -109,7 +109,7 @@
fds[1].events = POLLIN;
unsigned char buf[kBufferSize];
- auto h4 = test_vendor_lib::H4Packetizer(
+ auto h4 = rootcanal::H4Packetizer(
virtio_fd,
[](const std::vector<uint8_t>& /* raw_command */) {
LOG(ERROR)
diff --git a/host/commands/mk_cdisk/Android.bp b/guest/commands/dlkm_loader/Android.bp
similarity index 72%
copy from host/commands/mk_cdisk/Android.bp
copy to guest/commands/dlkm_loader/Android.bp
index a0cf8ba..ae91c02 100644
--- a/host/commands/mk_cdisk/Android.bp
+++ b/guest/commands/dlkm_loader/Android.bp
@@ -18,24 +18,16 @@
}
cc_binary {
- name: "mk_cdisk",
+ name: "dlkm_loader",
srcs: [
- "mk_cdisk.cc",
- ],
- shared_libs: [
- "libcuttlefish_fs",
- "libcuttlefish_utils",
- "libbase",
- "libjsoncpp",
- "liblog",
- "libz",
+ "dlkm_loader.cpp",
],
static_libs: [
- "libcdisk_spec",
- "libext2_uuid",
- "libimage_aggregator",
- "libprotobuf-cpp-lite",
- "libsparse",
+ "libbase",
+ "libmodprobe",
],
- defaults: ["cuttlefish_host"],
+ shared_libs: [
+ "liblog",
+ ],
+ defaults: ["cuttlefish_guest_only"]
}
diff --git a/guest/commands/dlkm_loader/dlkm_loader.cpp b/guest/commands/dlkm_loader/dlkm_loader.cpp
new file mode 100644
index 0000000..0d22225
--- /dev/null
+++ b/guest/commands/dlkm_loader/dlkm_loader.cpp
@@ -0,0 +1,26 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <android-base/logging.h>
+#include <modprobe/modprobe.h>
+
+int main(void) {
+ LOG(INFO) << "dlkm loader successfully initialized";
+ Modprobe m({"/vendor/lib/modules"}, "modules.load");
+ CHECK(m.LoadListedModules(true)) << "modules from vendor dlkm weren't loaded correctly";
+ LOG(INFO) << "module load count is " << m.GetModuleCount();
+ return 0;
+}
diff --git a/guest/commands/rename_netiface/Android.bp b/guest/commands/rename_netiface/Android.bp
index f7b71be..3cf4225 100644
--- a/guest/commands/rename_netiface/Android.bp
+++ b/guest/commands/rename_netiface/Android.bp
@@ -23,6 +23,7 @@
srcs: [
"main.cpp",
],
+ init_rc: ["rename_netiface.rc"],
shared_libs: [
"cuttlefish_net",
],
diff --git a/guest/commands/rename_netiface/rename_netiface.rc b/guest/commands/rename_netiface/rename_netiface.rc
new file mode 100644
index 0000000..5117320
--- /dev/null
+++ b/guest/commands/rename_netiface/rename_netiface.rc
@@ -0,0 +1,2 @@
+service rename_eth0 /vendor/bin/rename_netiface eth0 buried_eth0
+ oneshot
diff --git a/guest/commands/sensor_injection/Android.bp b/guest/commands/sensor_injection/Android.bp
index a29250a..665b116 100644
--- a/guest/commands/sensor_injection/Android.bp
+++ b/guest/commands/sensor_injection/Android.bp
@@ -6,10 +6,9 @@
name: "cuttlefish_sensor_injection",
srcs: ["main.cpp"],
shared_libs: [
- "[email protected]",
- "[email protected]",
+ "android.hardware.sensors-V1-ndk",
"libbase",
- "libbinder",
+ "libbinder_ndk",
"libhidlbase",
"liblog",
"libutils",
diff --git a/guest/commands/sensor_injection/main.cpp b/guest/commands/sensor_injection/main.cpp
index 6eadb4a..a78de33 100644
--- a/guest/commands/sensor_injection/main.cpp
+++ b/guest/commands/sensor_injection/main.cpp
@@ -16,55 +16,52 @@
#include <android-base/chrono_utils.h>
#include <android-base/logging.h>
-#include <binder/IServiceManager.h>
-#include <utils/StrongPointer.h>
+#include <android/binder_manager.h>
#include <utils/SystemClock.h>
#include <thread>
-#include "android/hardware/sensors/2.1/ISensors.h"
+#include <aidl/android/hardware/sensors/BnSensors.h>
-using android::sp;
-using android::hardware::sensors::V1_0::OperationMode;
-using android::hardware::sensors::V1_0::Result;
-using android::hardware::sensors::V1_0::SensorStatus;
-using android::hardware::sensors::V2_1::Event;
-using android::hardware::sensors::V2_1::ISensors;
-using android::hardware::sensors::V2_1::SensorInfo;
-using android::hardware::sensors::V2_1::SensorType;
+using aidl::android::hardware::sensors::Event;
+using aidl::android::hardware::sensors::ISensors;
+using aidl::android::hardware::sensors::SensorInfo;
+using aidl::android::hardware::sensors::SensorStatus;
+using aidl::android::hardware::sensors::SensorType;
-sp<ISensors> startSensorInjection() {
- const sp<ISensors> sensors = ISensors::getService();
+std::shared_ptr<ISensors> startSensorInjection() {
+ auto sensors = ISensors::fromBinder(ndk::SpAIBinder(
+ AServiceManager_getService("android.hardware.sensors.ISensors/default")));
if (sensors == nullptr) {
LOG(FATAL) << "Unable to get ISensors.";
}
// Place the ISensors HAL into DATA_INJECTION mode so that we can
// inject events.
- Result result = sensors->setOperationMode(OperationMode::DATA_INJECTION);
- if (result != Result::OK) {
+ auto result =
+ sensors->setOperationMode(ISensors::OperationMode::DATA_INJECTION);
+ if (!result.isOk()) {
LOG(FATAL) << "Unable to set ISensors operation mode to DATA_INJECTION: "
- << toString(result);
+ << result.getDescription();
}
return sensors;
}
-int getSensorHandle(SensorType type, const sp<ISensors> sensors) {
+int getSensorHandle(SensorType type, const std::shared_ptr<ISensors> sensors) {
// Find the first available sensor of the given type.
int handle = -1;
- const auto& getSensorsList_result =
- sensors->getSensorsList_2_1([&](const auto& list) {
- for (const SensorInfo& sensor : list) {
- if (sensor.type == type) {
- handle = sensor.sensorHandle;
- break;
- }
- }
- });
- if (!getSensorsList_result.isOk()) {
+ std::vector<SensorInfo> sensors_list;
+ auto result = sensors->getSensorsList(&sensors_list);
+ if (!result.isOk()) {
LOG(FATAL) << "Unable to get ISensors sensors list: "
- << getSensorsList_result.description();
+ << result.getDescription();
+ }
+ for (const SensorInfo& sensor : sensors_list) {
+ if (sensor.type == type) {
+ handle = sensor.sensorHandle;
+ break;
+ }
}
if (handle == -1) {
LOG(FATAL) << "Unable to find sensor.";
@@ -72,45 +69,46 @@
return handle;
}
-void endSensorInjection(const sp<ISensors> sensors) {
+void endSensorInjection(const std::shared_ptr<ISensors> sensors) {
// Return the ISensors HAL back to NORMAL mode.
- Result result = sensors->setOperationMode(OperationMode::NORMAL);
- if (result != Result::OK) {
+ auto result = sensors->setOperationMode(ISensors::OperationMode::NORMAL);
+ if (!result.isOk()) {
LOG(FATAL) << "Unable to set sensors operation mode to NORMAL: "
- << toString(result);
+ << result.getDescription();
}
}
// Inject ACCELEROMETER events to corresponding to a given physical
// device orientation: portrait or landscape.
void InjectOrientation(bool portrait) {
- sp<ISensors> sensors = startSensorInjection();
+ auto sensors = startSensorInjection();
int handle = getSensorHandle(SensorType::ACCELEROMETER, sensors);
// Create a base ISensors accelerometer event.
Event event;
event.sensorHandle = handle;
event.sensorType = SensorType::ACCELEROMETER;
+ Event::EventPayload::Vec3 vec3;
if (portrait) {
- event.u.vec3.x = 0;
- event.u.vec3.y = 9.2;
+ vec3.x = 0;
+ vec3.y = 9.2;
} else {
- event.u.vec3.x = 9.2;
- event.u.vec3.y = 0;
+ vec3.x = 9.2;
+ vec3.y = 0;
}
- event.u.vec3.z = 3.5;
- event.u.vec3.status = SensorStatus::ACCURACY_HIGH;
+ vec3.z = 3.5;
+ vec3.status = SensorStatus::ACCURACY_HIGH;
+ event.payload.set<Event::EventPayload::Tag::vec3>(vec3);
// Repeatedly inject accelerometer events. The WindowManager orientation
// listener responds to sustained accelerometer data, not just a single event.
android::base::Timer timer;
- Result result;
while (timer.duration() < 1s) {
event.timestamp = android::elapsedRealtimeNano();
- result = sensors->injectSensorData_2_1(event);
- if (result != Result::OK) {
+ auto result = sensors->injectSensorData(event);
+ if (!result.isOk()) {
LOG(FATAL) << "Unable to inject ISensors accelerometer event: "
- << toString(result);
+ << result.getDescription();
}
std::this_thread::sleep_for(10ms);
}
@@ -120,19 +118,20 @@
// Inject a single HINGE_ANGLE event at the given angle.
void InjectHingeAngle(int angle) {
- sp<ISensors> sensors = startSensorInjection();
+ auto sensors = startSensorInjection();
int handle = getSensorHandle(SensorType::HINGE_ANGLE, sensors);
// Create a base ISensors hinge_angle event.
Event event;
event.sensorHandle = handle;
event.sensorType = SensorType::HINGE_ANGLE;
- event.u.scalar = angle;
- event.u.vec3.status = SensorStatus::ACCURACY_HIGH;
+ event.payload.set<Event::EventPayload::Tag::scalar>((float)angle);
event.timestamp = android::elapsedRealtimeNano();
- Result result = sensors->injectSensorData_2_1(event);
- if (result != Result::OK) {
- LOG(FATAL) << "Unable to inject HINGE_ANGLE data: " << toString(result);
+
+ auto result = sensors->injectSensorData(event);
+ if (!result.isOk()) {
+ LOG(FATAL) << "Unable to inject HINGE_ANGLE data"
+ << result.getDescription();
}
endSensorInjection(sensors);
diff --git a/guest/commands/setup_wifi/Android.bp b/guest/commands/setup_wifi/Android.bp
index 21472b1..e7a85bb 100644
--- a/guest/commands/setup_wifi/Android.bp
+++ b/guest/commands/setup_wifi/Android.bp
@@ -23,6 +23,7 @@
srcs: [
"main.cpp",
],
+ init_rc: ["setup_wifi.rc"],
shared_libs: [
"cuttlefish_net",
"libbase",
diff --git a/guest/commands/setup_wifi/main.cpp b/guest/commands/setup_wifi/main.cpp
index 032f39e..4a91442 100644
--- a/guest/commands/setup_wifi/main.cpp
+++ b/guest/commands/setup_wifi/main.cpp
@@ -32,17 +32,15 @@
#include "common/libs/net/network_interface.h"
#include "common/libs/net/network_interface_manager.h"
-DEFINE_string(mac_address, "", "mac address to use for wlan0");
+DEFINE_string(mac_prefix, "", "mac prefix to use for wlan0");
-static std::array<unsigned char, 6> str_to_mac(const std::string& mac_str) {
+static std::array<unsigned char, 6> prefix_to_mac(
+ const std::string& mac_prefix) {
std::array<unsigned char, 6> mac;
- std::istringstream stream(mac_str);
- for (int i = 0; i < 6; i++) {
- int num;
- stream >> std::hex >> num;
- mac[i] = num;
- stream.get();
- }
+ int macPrefix = stoi(mac_prefix);
+ mac[0] = 0x02;
+ mac[1] = (macPrefix >> CHAR_BIT) & 0xFF;
+ mac[2] = macPrefix & 0xFF;
return mac;
}
@@ -51,7 +49,8 @@
auto factory = cuttlefish::NetlinkClientFactory::Default();
std::unique_ptr<cuttlefish::NetlinkClient> nl(factory->New(NETLINK_ROUTE));
- LOG(INFO) << "Setting " << source << " mac address to " << FLAGS_mac_address;
+ LOG(INFO) << "Setting " << source << " mac address based on "
+ << FLAGS_mac_prefix;
int32_t index = if_nametoindex(source.c_str());
// Setting the address is available in RTM_SETLINK, but not RTM_NEWLINK.
// https://elixir.bootlin.com/linux/v5.4.44/source/net/core/rtnetlink.c#L2785
@@ -64,7 +63,7 @@
.ifi_index = index,
.ifi_change = 0xFFFFFFFF,
});
- fix_mac_request.AddMacAddress(str_to_mac(FLAGS_mac_address));
+ fix_mac_request.AddMacAddress(prefix_to_mac(FLAGS_mac_prefix));
bool fix_mac = nl->Send(fix_mac_request);
if (!fix_mac) {
LOG(ERROR) << "setup_network: could not fix mac address";
@@ -131,10 +130,10 @@
}
int main(int argc, char** argv) {
- char wifi_address[PROPERTY_VALUE_MAX + 1];
- property_get("ro.boot.wifi_mac_address", wifi_address, "");
+ char wifi_mac_prefix[PROPERTY_VALUE_MAX + 1];
+ property_get("ro.boot.wifi_mac_prefix", wifi_mac_prefix, "");
- SetCommandLineOptionWithMode("mac_address", wifi_address,
+ SetCommandLineOptionWithMode("mac_prefix", wifi_mac_prefix,
google::FlagSettingMode::SET_FLAGS_DEFAULT);
gflags::ParseCommandLineFlags(&argc, &argv, true);
diff --git a/guest/commands/setup_wifi/setup_wifi.rc b/guest/commands/setup_wifi/setup_wifi.rc
new file mode 100644
index 0000000..a2f1eb5
--- /dev/null
+++ b/guest/commands/setup_wifi/setup_wifi.rc
@@ -0,0 +1,2 @@
+service setup_wifi /vendor/bin/setup_wifi
+ oneshot
diff --git a/guest/hals/audio/audio_hw.c b/guest/hals/audio/audio_hw.c
deleted file mode 100644
index c0c705a..0000000
--- a/guest/hals/audio/audio_hw.c
+++ /dev/null
@@ -1,1854 +0,0 @@
-/*
- * Copyright (C) 2012 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- *
- * This code was forked from device/generic/goldfish/audio/audio_hw.c
- *
- * At the time of forking, the code was identical except that a fallback
- * to a legacy HAL which does not use ALSA was removed, and the dependency
- * on libdl was also removed.
- */
-
-#define LOG_TAG "audio_hw_generic"
-
-#include <assert.h>
-#include <errno.h>
-#include <inttypes.h>
-#include <pthread.h>
-#include <stdint.h>
-#include <stdlib.h>
-#include <sys/time.h>
-#include <dlfcn.h>
-#include <fcntl.h>
-#include <unistd.h>
-
-#include <log/log.h>
-#include <cutils/list.h>
-#include <cutils/str_parms.h>
-
-#include <hardware/hardware.h>
-#include <system/audio.h>
-#include <hardware/audio.h>
-#include <tinyalsa/asoundlib.h>
-
-#define PCM_CARD 0
-#define PCM_DEVICE 0
-
-#define OUT_PERIOD_MS 10
-#define OUT_PERIOD_COUNT 4
-
-#define IN_PERIOD_MS 10
-#define IN_PERIOD_COUNT 4
-
-struct generic_audio_device {
- struct audio_hw_device device; // Constant after init
- pthread_mutex_t lock;
- bool mic_mute; // Protected by this->lock
- struct mixer* mixer; // Protected by this->lock
- struct listnode out_streams; // Record for output streams, protected by this->lock
- struct listnode in_streams; // Record for input streams, protected by this->lock
- audio_patch_handle_t next_patch_handle; // Protected by this->lock
-};
-
-/* If not NULL, this is a pointer to the fallback module.
- * This really is the original goldfish audio device /dev/eac which we will use
- * if no alsa devices are detected.
- */
-static int adev_get_mic_mute(const struct audio_hw_device *dev, bool *state);
-static int adev_get_microphones(const audio_hw_device_t *dev,
- struct audio_microphone_characteristic_t *mic_array,
- size_t *mic_count);
-
-
-typedef struct audio_vbuffer {
- pthread_mutex_t lock;
- uint8_t * data;
- size_t frame_size;
- size_t frame_count;
- size_t head;
- size_t tail;
- size_t live;
-} audio_vbuffer_t;
-
-static int audio_vbuffer_init (audio_vbuffer_t * audio_vbuffer, size_t frame_count,
- size_t frame_size) {
- if (!audio_vbuffer) {
- return -EINVAL;
- }
- audio_vbuffer->frame_size = frame_size;
- audio_vbuffer->frame_count = frame_count;
- size_t bytes = frame_count * frame_size;
- audio_vbuffer->data = calloc(bytes, 1);
- if (!audio_vbuffer->data) {
- return -ENOMEM;
- }
- audio_vbuffer->head = 0;
- audio_vbuffer->tail = 0;
- audio_vbuffer->live = 0;
- pthread_mutex_init (&audio_vbuffer->lock, (const pthread_mutexattr_t *) NULL);
- return 0;
-}
-
-static int audio_vbuffer_destroy (audio_vbuffer_t * audio_vbuffer) {
- if (!audio_vbuffer) {
- return -EINVAL;
- }
- free(audio_vbuffer->data);
- pthread_mutex_destroy(&audio_vbuffer->lock);
- return 0;
-}
-
-static int audio_vbuffer_live (audio_vbuffer_t * audio_vbuffer) {
- if (!audio_vbuffer) {
- return -EINVAL;
- }
- pthread_mutex_lock (&audio_vbuffer->lock);
- int live = audio_vbuffer->live;
- pthread_mutex_unlock (&audio_vbuffer->lock);
- return live;
-}
-
-#define MIN(a,b) (((a)<(b))?(a):(b))
-static size_t audio_vbuffer_write (audio_vbuffer_t * audio_vbuffer, const void * buffer, size_t frame_count) {
- size_t frames_written = 0;
- pthread_mutex_lock (&audio_vbuffer->lock);
-
- while (frame_count != 0) {
- int frames = 0;
- if (audio_vbuffer->live == 0 || audio_vbuffer->head > audio_vbuffer->tail) {
- frames = MIN(frame_count, audio_vbuffer->frame_count - audio_vbuffer->head);
- } else if (audio_vbuffer->head < audio_vbuffer->tail) {
- frames = MIN(frame_count, audio_vbuffer->tail - (audio_vbuffer->head));
- } else {
- // Full
- break;
- }
- memcpy(&audio_vbuffer->data[audio_vbuffer->head*audio_vbuffer->frame_size],
- &((uint8_t*)buffer)[frames_written*audio_vbuffer->frame_size],
- frames*audio_vbuffer->frame_size);
- audio_vbuffer->live += frames;
- frames_written += frames;
- frame_count -= frames;
- audio_vbuffer->head = (audio_vbuffer->head + frames) % audio_vbuffer->frame_count;
- }
-
- pthread_mutex_unlock (&audio_vbuffer->lock);
- return frames_written;
-}
-
-static size_t audio_vbuffer_read (audio_vbuffer_t * audio_vbuffer, void * buffer, size_t frame_count) {
- size_t frames_read = 0;
- pthread_mutex_lock (&audio_vbuffer->lock);
-
- while (frame_count != 0) {
- int frames = 0;
- if (audio_vbuffer->live == audio_vbuffer->frame_count ||
- audio_vbuffer->tail > audio_vbuffer->head) {
- frames = MIN(frame_count, audio_vbuffer->frame_count - audio_vbuffer->tail);
- } else if (audio_vbuffer->tail < audio_vbuffer->head) {
- frames = MIN(frame_count, audio_vbuffer->head - audio_vbuffer->tail);
- } else {
- break;
- }
- memcpy(&((uint8_t*)buffer)[frames_read*audio_vbuffer->frame_size],
- &audio_vbuffer->data[audio_vbuffer->tail*audio_vbuffer->frame_size],
- frames*audio_vbuffer->frame_size);
- audio_vbuffer->live -= frames;
- frames_read += frames;
- frame_count -= frames;
- audio_vbuffer->tail = (audio_vbuffer->tail + frames) % audio_vbuffer->frame_count;
- }
-
- pthread_mutex_unlock (&audio_vbuffer->lock);
- return frames_read;
-}
-
-struct generic_stream_out {
- struct audio_stream_out stream; // Constant after init
- pthread_mutex_t lock;
- struct generic_audio_device *dev; // Constant after init
- uint32_t num_devices; // Protected by this->lock
- audio_devices_t devices[AUDIO_PATCH_PORTS_MAX]; // Protected by this->lock
- struct audio_config req_config; // Constant after init
- struct pcm_config pcm_config; // Constant after init
- audio_vbuffer_t buffer; // Constant after init
-
- // Time & Position Keeping
- bool standby; // Protected by this->lock
- uint64_t underrun_position; // Protected by this->lock
- struct timespec underrun_time; // Protected by this->lock
- uint64_t last_write_time_us; // Protected by this->lock
- uint64_t frames_total_buffered; // Protected by this->lock
- uint64_t frames_written; // Protected by this->lock
- uint64_t frames_rendered; // Protected by this->lock
-
- // Worker
- pthread_t worker_thread; // Constant after init
- pthread_cond_t worker_wake; // Protected by this->lock
- bool worker_standby; // Protected by this->lock
- bool worker_exit; // Protected by this->lock
-
- audio_io_handle_t handle; // Constant after init
- audio_patch_handle_t patch_handle; // Protected by this->dev->lock
-
- struct listnode stream_node; // Protected by this->dev->lock
-};
-
-struct generic_stream_in {
- struct audio_stream_in stream; // Constant after init
- pthread_mutex_t lock;
- struct generic_audio_device *dev; // Constant after init
- audio_devices_t device; // Protected by this->lock
- struct audio_config req_config; // Constant after init
- struct pcm *pcm; // Protected by this->lock
- struct pcm_config pcm_config; // Constant after init
- int16_t *stereo_to_mono_buf; // Protected by this->lock
- size_t stereo_to_mono_buf_size; // Protected by this->lock
- audio_vbuffer_t buffer; // Protected by this->lock
-
- // Time & Position Keeping
- bool standby; // Protected by this->lock
- int64_t standby_position; // Protected by this->lock
- struct timespec standby_exit_time;// Protected by this->lock
- int64_t standby_frames_read; // Protected by this->lock
-
- // Worker
- pthread_t worker_thread; // Constant after init
- pthread_cond_t worker_wake; // Protected by this->lock
- bool worker_standby; // Protected by this->lock
- bool worker_exit; // Protected by this->lock
-
- audio_io_handle_t handle; // Constant after init
- audio_patch_handle_t patch_handle; // Protected by this->dev->lock
-
- struct listnode stream_node; // Protected by this->dev->lock
-};
-
-static struct pcm_config pcm_config_out = {
- .channels = 2,
- .rate = 0,
- .period_size = 0,
- .period_count = OUT_PERIOD_COUNT,
- .format = PCM_FORMAT_S16_LE,
- .start_threshold = 0,
-};
-
-static struct pcm_config pcm_config_in = {
- .channels = 2,
- .rate = 0,
- .period_size = 0,
- .period_count = IN_PERIOD_COUNT,
- .format = PCM_FORMAT_S16_LE,
- .start_threshold = 0,
- .stop_threshold = INT_MAX,
-};
-
-static pthread_mutex_t adev_init_lock = PTHREAD_MUTEX_INITIALIZER;
-static unsigned int audio_device_ref_count = 0;
-
-static uint32_t out_get_sample_rate(const struct audio_stream *stream)
-{
- struct generic_stream_out *out = (struct generic_stream_out *)stream;
- return out->req_config.sample_rate;
-}
-
-static int out_set_sample_rate(struct audio_stream *stream, uint32_t rate)
-{
- return -ENOSYS;
-}
-
-static size_t out_get_buffer_size(const struct audio_stream *stream)
-{
- struct generic_stream_out *out = (struct generic_stream_out *)stream;
- int size = out->pcm_config.period_size *
- audio_stream_out_frame_size(&out->stream);
-
- return size;
-}
-
-static audio_channel_mask_t out_get_channels(const struct audio_stream *stream)
-{
- struct generic_stream_out *out = (struct generic_stream_out *)stream;
- return out->req_config.channel_mask;
-}
-
-static audio_format_t out_get_format(const struct audio_stream *stream)
-{
- struct generic_stream_out *out = (struct generic_stream_out *)stream;
-
- return out->req_config.format;
-}
-
-static int out_set_format(struct audio_stream *stream, audio_format_t format)
-{
- return -ENOSYS;
-}
-
-static int out_dump(const struct audio_stream *stream, int fd)
-{
- struct generic_stream_out *out = (struct generic_stream_out *)stream;
- pthread_mutex_lock(&out->lock);
- dprintf(fd, "\tout_dump:\n"
- "\t\tsample rate: %u\n"
- "\t\tbuffer size: %zu\n"
- "\t\tchannel mask: %08x\n"
- "\t\tformat: %d\n"
- "\t\tdevice(s): ",
- out_get_sample_rate(stream),
- out_get_buffer_size(stream),
- out_get_channels(stream),
- out_get_format(stream));
- if (out->num_devices == 0) {
- dprintf(fd, "%08x\n", AUDIO_DEVICE_NONE);
- } else {
- for (uint32_t i = 0; i < out->num_devices; i++) {
- if (i != 0) {
- dprintf(fd, ", ");
- }
- dprintf(fd, "%08x", out->devices[i]);
- }
- dprintf(fd, "\n");
- }
- dprintf(fd, "\t\taudio dev: %p\n\n", out->dev);
- pthread_mutex_unlock(&out->lock);
- return 0;
-}
-
-static int out_set_parameters(struct audio_stream *stream, const char *kvpairs)
-{
- struct str_parms *parms;
- char value[32];
- int success;
- int ret = -EINVAL;
-
- if (kvpairs == NULL || kvpairs[0] == 0) {
- return 0;
- }
- parms = str_parms_create_str(kvpairs);
- success = str_parms_get_str(parms, AUDIO_PARAMETER_STREAM_ROUTING,
- value, sizeof(value));
- // As the hal version is 3.0, it must not use set parameters API to set audio devices.
- // Instead, it should use create_audio_patch API.
- assert(("Must not use set parameters API to set audio devices", success < 0));
-
- if (str_parms_has_key(parms, AUDIO_PARAMETER_STREAM_FORMAT)) {
- // match the return value of out_set_format
- ret = -ENOSYS;
- }
-
- str_parms_destroy(parms);
-
- if (ret == -EINVAL) {
- ALOGW("%s(), unsupported parameter %s", __func__, kvpairs);
- // There is not any key supported for set_parameters API.
- // Return error when there is non-null value passed in.
- }
- return ret;
-}
-
-static char * out_get_parameters(const struct audio_stream *stream, const char *keys)
-{
- struct generic_stream_out *out = (struct generic_stream_out *)stream;
- struct str_parms *query = str_parms_create_str(keys);
- char *str = NULL;
- char value[256];
- struct str_parms *reply = str_parms_create();
- int ret;
- bool get = false;
-
- ret = str_parms_get_str(query, AUDIO_PARAMETER_STREAM_ROUTING, value, sizeof(value));
- if (ret >= 0) {
- pthread_mutex_lock(&out->lock);
- audio_devices_t device = AUDIO_DEVICE_NONE;
- for (uint32_t i = 0; i < out->num_devices; i++) {
- device |= out->devices[i];
- }
- str_parms_add_int(reply, AUDIO_PARAMETER_STREAM_ROUTING, device);
- pthread_mutex_unlock(&out->lock);
- get = true;
- }
-
- if (str_parms_has_key(query, AUDIO_PARAMETER_STREAM_SUP_FORMATS)) {
- value[0] = 0;
- strcat(value, "AUDIO_FORMAT_PCM_16_BIT");
- str_parms_add_str(reply, AUDIO_PARAMETER_STREAM_SUP_FORMATS, value);
- get = true;
- }
-
- if (str_parms_has_key(query, AUDIO_PARAMETER_STREAM_FORMAT)) {
- value[0] = 0;
- strcat(value, "AUDIO_FORMAT_PCM_16_BIT");
- str_parms_add_str(reply, AUDIO_PARAMETER_STREAM_FORMAT, value);
- get = true;
- }
-
- if (get) {
- str = str_parms_to_str(reply);
- }
- else {
- ALOGD("%s Unsupported paramter: %s", __FUNCTION__, keys);
- }
-
- str_parms_destroy(query);
- str_parms_destroy(reply);
- return str;
-}
-
-static uint32_t out_get_latency(const struct audio_stream_out *stream)
-{
- struct generic_stream_out *out = (struct generic_stream_out *)stream;
- return (out->pcm_config.period_size * 1000) / out->pcm_config.rate;
-}
-
-static int out_set_volume(struct audio_stream_out *stream, float left,
- float right)
-{
- return -ENOSYS;
-}
-
-static void *out_write_worker(void * args)
-{
- struct generic_stream_out *out = (struct generic_stream_out *)args;
- struct pcm *pcm = NULL;
- uint8_t *buffer = NULL;
- int buffer_frames;
- int buffer_size;
- bool restart = false;
- bool shutdown = false;
- while (true) {
- pthread_mutex_lock(&out->lock);
- while (out->worker_standby || restart) {
- restart = false;
- if (pcm) {
- pcm_close(pcm); // Frees pcm
- pcm = NULL;
- free(buffer);
- buffer=NULL;
- }
- if (out->worker_exit) {
- break;
- }
- pthread_cond_wait(&out->worker_wake, &out->lock);
- }
-
- if (out->worker_exit) {
- if (!out->worker_standby) {
- ALOGE("Out worker not in standby before exiting");
- }
- shutdown = true;
- }
-
- while (!shutdown && audio_vbuffer_live(&out->buffer) == 0) {
- pthread_cond_wait(&out->worker_wake, &out->lock);
- }
-
- if (shutdown) {
- pthread_mutex_unlock(&out->lock);
- break;
- }
-
- if (!pcm) {
- pcm = pcm_open(PCM_CARD, PCM_DEVICE,
- PCM_OUT | PCM_MONOTONIC, &out->pcm_config);
- if (!pcm_is_ready(pcm)) {
- ALOGE("pcm_open(out) failed: %s: channels %d format %d rate %d",
- pcm_get_error(pcm),
- out->pcm_config.channels,
- out->pcm_config.format,
- out->pcm_config.rate
- );
- pthread_mutex_unlock(&out->lock);
- break;
- }
- buffer_frames = out->pcm_config.period_size;
- buffer_size = pcm_frames_to_bytes(pcm, buffer_frames);
- buffer = malloc(buffer_size);
- if (!buffer) {
- ALOGE("could not allocate write buffer");
- pthread_mutex_unlock(&out->lock);
- break;
- }
- }
- int frames = audio_vbuffer_read(&out->buffer, buffer, buffer_frames);
- pthread_mutex_unlock(&out->lock);
- int ret = pcm_write(pcm, buffer, pcm_frames_to_bytes(pcm, frames));
- if (ret != 0) {
- ALOGE("pcm_write failed %s", pcm_get_error(pcm));
- restart = true;
- }
- }
- if (buffer) {
- free(buffer);
- }
-
- return NULL;
-}
-
-// Call with in->lock held
-static void get_current_output_position(struct generic_stream_out *out,
- uint64_t * position,
- struct timespec * timestamp) {
- struct timespec curtime = { .tv_sec = 0, .tv_nsec = 0 };
- clock_gettime(CLOCK_MONOTONIC, &curtime);
- const int64_t now_us = (curtime.tv_sec * 1000000000LL + curtime.tv_nsec) / 1000;
- if (timestamp) {
- *timestamp = curtime;
- }
- int64_t position_since_underrun;
- if (out->standby) {
- position_since_underrun = 0;
- } else {
- const int64_t first_us = (out->underrun_time.tv_sec * 1000000000LL +
- out->underrun_time.tv_nsec) / 1000;
- position_since_underrun = (now_us - first_us) *
- out_get_sample_rate(&out->stream.common) /
- 1000000;
- if (position_since_underrun < 0) {
- position_since_underrun = 0;
- }
- }
- *position = out->underrun_position + position_since_underrun;
-
- // The device will reuse the same output stream leading to periods of
- // underrun.
- if (*position > out->frames_written) {
- ALOGW("Not supplying enough data to HAL, expected position %" PRIu64 " , only wrote "
- "%" PRIu64,
- *position, out->frames_written);
-
- *position = out->frames_written;
- out->underrun_position = *position;
- out->underrun_time = curtime;
- out->frames_total_buffered = 0;
- }
-}
-
-
-static ssize_t out_write(struct audio_stream_out *stream, const void *buffer,
- size_t bytes)
-{
- struct generic_stream_out *out = (struct generic_stream_out *)stream;
- const size_t frames = bytes / audio_stream_out_frame_size(stream);
-
- pthread_mutex_lock(&out->lock);
-
- if (out->worker_standby) {
- out->worker_standby = false;
- }
-
- uint64_t current_position;
- struct timespec current_time;
-
- get_current_output_position(out, ¤t_position, ¤t_time);
- const uint64_t now_us = (current_time.tv_sec * 1000000000LL +
- current_time.tv_nsec) / 1000;
- if (out->standby) {
- out->standby = false;
- out->underrun_time = current_time;
- out->frames_rendered = 0;
- out->frames_total_buffered = 0;
- }
-
- size_t frames_written = audio_vbuffer_write(&out->buffer, buffer, frames);
- pthread_cond_signal(&out->worker_wake);
-
- /* Implementation just consumes bytes if we start getting backed up */
- out->frames_written += frames;
- out->frames_rendered += frames;
- out->frames_total_buffered += frames;
-
- // We simulate the audio device blocking when it's write buffers become
- // full.
-
- // At the beginning or after an underrun, try to fill up the vbuffer.
- // This will be throttled by the PlaybackThread
- int frames_sleep = out->frames_total_buffered < out->buffer.frame_count ? 0 : frames;
-
- uint64_t sleep_time_us = frames_sleep * 1000000LL /
- out_get_sample_rate(&stream->common);
-
- // If the write calls are delayed, subtract time off of the sleep to
- // compensate
- uint64_t time_since_last_write_us = now_us - out->last_write_time_us;
- if (time_since_last_write_us < sleep_time_us) {
- sleep_time_us -= time_since_last_write_us;
- } else {
- sleep_time_us = 0;
- }
- out->last_write_time_us = now_us + sleep_time_us;
-
- pthread_mutex_unlock(&out->lock);
-
- if (sleep_time_us > 0) {
- usleep(sleep_time_us);
- }
-
- if (frames_written < frames) {
- ALOGW("Hardware backing HAL too slow, could only write %zu of %zu frames", frames_written, frames);
- }
-
- /* Always consume all bytes */
- return bytes;
-}
-
-static int out_get_presentation_position(const struct audio_stream_out *stream,
- uint64_t *frames, struct timespec *timestamp)
-
-{
- if (stream == NULL || frames == NULL || timestamp == NULL) {
- return -EINVAL;
- }
- struct generic_stream_out *out = (struct generic_stream_out *)stream;
-
- pthread_mutex_lock(&out->lock);
- get_current_output_position(out, frames, timestamp);
- pthread_mutex_unlock(&out->lock);
-
- return 0;
-}
-
-static int out_get_render_position(const struct audio_stream_out *stream,
- uint32_t *dsp_frames)
-{
- if (stream == NULL || dsp_frames == NULL) {
- return -EINVAL;
- }
- struct generic_stream_out *out = (struct generic_stream_out *)stream;
- pthread_mutex_lock(&out->lock);
- *dsp_frames = out->frames_rendered;
- pthread_mutex_unlock(&out->lock);
- return 0;
-}
-
-// Must be called with out->lock held
-static void do_out_standby(struct generic_stream_out *out)
-{
- int frames_sleep = 0;
- uint64_t sleep_time_us = 0;
- if (out->standby) {
- return;
- }
- while (true) {
- get_current_output_position(out, &out->underrun_position, NULL);
- frames_sleep = out->frames_written - out->underrun_position;
-
- if (frames_sleep == 0) {
- break;
- }
-
- sleep_time_us = frames_sleep * 1000000LL /
- out_get_sample_rate(&out->stream.common);
-
- pthread_mutex_unlock(&out->lock);
- usleep(sleep_time_us);
- pthread_mutex_lock(&out->lock);
- }
- out->worker_standby = true;
- out->standby = true;
-}
-
-static int out_standby(struct audio_stream *stream)
-{
- struct generic_stream_out *out = (struct generic_stream_out *)stream;
- pthread_mutex_lock(&out->lock);
- do_out_standby(out);
- pthread_mutex_unlock(&out->lock);
- return 0;
-}
-
-static int out_add_audio_effect(const struct audio_stream *stream, effect_handle_t effect)
-{
- // out_add_audio_effect is a no op
- return 0;
-}
-
-static int out_remove_audio_effect(const struct audio_stream *stream, effect_handle_t effect)
-{
- // out_remove_audio_effect is a no op
- return 0;
-}
-
-static int out_get_next_write_timestamp(const struct audio_stream_out *stream,
- int64_t *timestamp)
-{
- return -ENOSYS;
-}
-
-static uint32_t in_get_sample_rate(const struct audio_stream *stream)
-{
- struct generic_stream_in *in = (struct generic_stream_in *)stream;
- return in->req_config.sample_rate;
-}
-
-static int in_set_sample_rate(struct audio_stream *stream, uint32_t rate)
-{
- return -ENOSYS;
-}
-
-static int refine_output_parameters(uint32_t *sample_rate, audio_format_t *format, audio_channel_mask_t *channel_mask)
-{
- static const uint32_t sample_rates [] = {8000,11025,16000,22050,24000,32000,
- 44100,48000};
- static const int sample_rates_count = sizeof(sample_rates)/sizeof(uint32_t);
- bool inval = false;
- if (*format != AUDIO_FORMAT_PCM_16_BIT) {
- *format = AUDIO_FORMAT_PCM_16_BIT;
- inval = true;
- }
-
- int channel_count = popcount(*channel_mask);
- if (channel_count != 1 && channel_count != 2) {
- *channel_mask = AUDIO_CHANNEL_IN_STEREO;
- inval = true;
- }
-
- int i;
- for (i = 0; i < sample_rates_count; i++) {
- if (*sample_rate < sample_rates[i]) {
- *sample_rate = sample_rates[i];
- inval=true;
- break;
- }
- else if (*sample_rate == sample_rates[i]) {
- break;
- }
- else if (i == sample_rates_count-1) {
- // Cap it to the highest rate we support
- *sample_rate = sample_rates[i];
- inval=true;
- }
- }
-
- if (inval) {
- return -EINVAL;
- }
- return 0;
-}
-
-static int refine_input_parameters(uint32_t *sample_rate, audio_format_t *format, audio_channel_mask_t *channel_mask)
-{
- static const uint32_t sample_rates [] = {8000, 11025, 16000, 22050, 44100, 48000};
- static const int sample_rates_count = sizeof(sample_rates)/sizeof(uint32_t);
- bool inval = false;
- // Only PCM_16_bit is supported. If this is changed, stereo to mono drop
- // must be fixed in in_read
- if (*format != AUDIO_FORMAT_PCM_16_BIT) {
- *format = AUDIO_FORMAT_PCM_16_BIT;
- inval = true;
- }
-
- int channel_count = popcount(*channel_mask);
- if (channel_count != 1 && channel_count != 2) {
- *channel_mask = AUDIO_CHANNEL_IN_STEREO;
- inval = true;
- }
-
- int i;
- for (i = 0; i < sample_rates_count; i++) {
- if (*sample_rate < sample_rates[i]) {
- *sample_rate = sample_rates[i];
- inval=true;
- break;
- }
- else if (*sample_rate == sample_rates[i]) {
- break;
- }
- else if (i == sample_rates_count-1) {
- // Cap it to the highest rate we support
- *sample_rate = sample_rates[i];
- inval=true;
- }
- }
-
- if (inval) {
- return -EINVAL;
- }
- return 0;
-}
-
-static int check_input_parameters(uint32_t sample_rate, audio_format_t format,
- audio_channel_mask_t channel_mask)
-{
- return refine_input_parameters(&sample_rate, &format, &channel_mask);
-}
-
-static size_t get_input_buffer_size(uint32_t sample_rate, audio_format_t format,
- audio_channel_mask_t channel_mask)
-{
- size_t size;
- int channel_count = popcount(channel_mask);
- if (check_input_parameters(sample_rate, format, channel_mask) != 0)
- return 0;
-
- size = sample_rate*IN_PERIOD_MS/1000;
- // Audioflinger expects audio buffers to be multiple of 16 frames
- size = ((size + 15) / 16) * 16;
- size *= sizeof(short) * channel_count;
-
- return size;
-}
-
-
-static size_t in_get_buffer_size(const struct audio_stream *stream)
-{
- struct generic_stream_in *in = (struct generic_stream_in *)stream;
- int size = get_input_buffer_size(in->req_config.sample_rate,
- in->req_config.format,
- in->req_config.channel_mask);
-
- return size;
-}
-
-static audio_channel_mask_t in_get_channels(const struct audio_stream *stream)
-{
- struct generic_stream_in *in = (struct generic_stream_in *)stream;
- return in->req_config.channel_mask;
-}
-
-static audio_format_t in_get_format(const struct audio_stream *stream)
-{
- struct generic_stream_in *in = (struct generic_stream_in *)stream;
- return in->req_config.format;
-}
-
-static int in_set_format(struct audio_stream *stream, audio_format_t format)
-{
- return -ENOSYS;
-}
-
-static int in_dump(const struct audio_stream *stream, int fd)
-{
- struct generic_stream_in *in = (struct generic_stream_in *)stream;
-
- pthread_mutex_lock(&in->lock);
- dprintf(fd, "\tin_dump:\n"
- "\t\tsample rate: %u\n"
- "\t\tbuffer size: %zu\n"
- "\t\tchannel mask: %08x\n"
- "\t\tformat: %d\n"
- "\t\tdevice: %08x\n"
- "\t\taudio dev: %p\n\n",
- in_get_sample_rate(stream),
- in_get_buffer_size(stream),
- in_get_channels(stream),
- in_get_format(stream),
- in->device,
- in->dev);
- pthread_mutex_unlock(&in->lock);
- return 0;
-}
-
-static int in_set_parameters(struct audio_stream *stream, const char *kvpairs)
-{
- struct str_parms *parms;
- char value[32];
- int success;
- int ret = -EINVAL;
-
- if (kvpairs == NULL || kvpairs[0] == 0) {
- return 0;
- }
- parms = str_parms_create_str(kvpairs);
- success = str_parms_get_str(parms, AUDIO_PARAMETER_STREAM_ROUTING,
- value, sizeof(value));
- // As the hal version is 3.0, it must not use set parameters API to set audio device.
- // Instead, it should use create_audio_patch API.
- assert(("Must not use set parameters API to set audio devices", success < 0));
-
- if (str_parms_has_key(parms, AUDIO_PARAMETER_STREAM_FORMAT)) {
- // match the return value of in_set_format
- ret = -ENOSYS;
- }
-
- str_parms_destroy(parms);
-
- if (ret == -EINVAL) {
- ALOGW("%s(), unsupported parameter %s", __func__, kvpairs);
- // There is not any key supported for set_parameters API.
- // Return error when there is non-null value passed in.
- }
- return ret;
-}
-
-static char * in_get_parameters(const struct audio_stream *stream,
- const char *keys)
-{
- struct generic_stream_in *in = (struct generic_stream_in *)stream;
- struct str_parms *query = str_parms_create_str(keys);
- char *str = NULL;
- char value[256];
- struct str_parms *reply = str_parms_create();
- int ret;
- bool get = false;
-
- ret = str_parms_get_str(query, AUDIO_PARAMETER_STREAM_ROUTING, value, sizeof(value));
- if (ret >= 0) {
- str_parms_add_int(reply, AUDIO_PARAMETER_STREAM_ROUTING, in->device);
- get = true;
- }
-
- if (str_parms_has_key(query, AUDIO_PARAMETER_STREAM_SUP_FORMATS)) {
- value[0] = 0;
- strcat(value, "AUDIO_FORMAT_PCM_16_BIT");
- str_parms_add_str(reply, AUDIO_PARAMETER_STREAM_SUP_FORMATS, value);
- get = true;
- }
-
- if (str_parms_has_key(query, AUDIO_PARAMETER_STREAM_FORMAT)) {
- value[0] = 0;
- strcat(value, "AUDIO_FORMAT_PCM_16_BIT");
- str_parms_add_str(reply, AUDIO_PARAMETER_STREAM_FORMAT, value);
- get = true;
- }
-
- if (get) {
- str = str_parms_to_str(reply);
- }
- else {
- ALOGD("%s Unsupported paramter: %s", __FUNCTION__, keys);
- }
-
- str_parms_destroy(query);
- str_parms_destroy(reply);
- return str;
-}
-
-static int in_set_gain(struct audio_stream_in *stream, float gain)
-{
- // in_set_gain is a no op
- return 0;
-}
-
-// Call with in->lock held
-static void get_current_input_position(struct generic_stream_in *in,
- int64_t * position,
- struct timespec * timestamp) {
- struct timespec t = { .tv_sec = 0, .tv_nsec = 0 };
- clock_gettime(CLOCK_MONOTONIC, &t);
- const int64_t now_us = (t.tv_sec * 1000000000LL + t.tv_nsec) / 1000;
- if (timestamp) {
- *timestamp = t;
- }
- int64_t position_since_standby;
- if (in->standby) {
- position_since_standby = 0;
- } else {
- const int64_t first_us = (in->standby_exit_time.tv_sec * 1000000000LL +
- in->standby_exit_time.tv_nsec) / 1000;
- position_since_standby = (now_us - first_us) *
- in_get_sample_rate(&in->stream.common) /
- 1000000;
- if (position_since_standby < 0) {
- position_since_standby = 0;
- }
- }
- *position = in->standby_position + position_since_standby;
-}
-
-// Must be called with in->lock held
-static void do_in_standby(struct generic_stream_in *in)
-{
- if (in->standby) {
- return;
- }
- in->worker_standby = true;
- get_current_input_position(in, &in->standby_position, NULL);
- in->standby = true;
-}
-
-static int in_standby(struct audio_stream *stream)
-{
- struct generic_stream_in *in = (struct generic_stream_in *)stream;
- pthread_mutex_lock(&in->lock);
- do_in_standby(in);
- pthread_mutex_unlock(&in->lock);
- return 0;
-}
-
-static void *in_read_worker(void * args)
-{
- struct generic_stream_in *in = (struct generic_stream_in *)args;
- struct pcm *pcm = NULL;
- uint8_t *buffer = NULL;
- size_t buffer_frames;
- int buffer_size;
-
- bool restart = false;
- bool shutdown = false;
- while (true) {
- pthread_mutex_lock(&in->lock);
- while (in->worker_standby || restart) {
- restart = false;
- if (pcm) {
- pcm_close(pcm); // Frees pcm
- pcm = NULL;
- free(buffer);
- buffer=NULL;
- }
- if (in->worker_exit) {
- break;
- }
- pthread_cond_wait(&in->worker_wake, &in->lock);
- }
-
- if (in->worker_exit) {
- if (!in->worker_standby) {
- ALOGE("In worker not in standby before exiting");
- }
- shutdown = true;
- }
- if (shutdown) {
- pthread_mutex_unlock(&in->lock);
- break;
- }
- if (!pcm) {
- pcm = pcm_open(PCM_CARD, PCM_DEVICE,
- PCM_IN | PCM_MONOTONIC, &in->pcm_config);
- if (!pcm_is_ready(pcm)) {
- ALOGE("pcm_open(in) failed: %s: channels %d format %d rate %d",
- pcm_get_error(pcm),
- in->pcm_config.channels,
- in->pcm_config.format,
- in->pcm_config.rate
- );
- pthread_mutex_unlock(&in->lock);
- break;
- }
- buffer_frames = in->pcm_config.period_size;
- buffer_size = pcm_frames_to_bytes(pcm, buffer_frames);
- buffer = malloc(buffer_size);
- if (!buffer) {
- ALOGE("could not allocate worker read buffer");
- pthread_mutex_unlock(&in->lock);
- break;
- }
- }
- pthread_mutex_unlock(&in->lock);
- int ret = pcm_read(pcm, buffer, pcm_frames_to_bytes(pcm, buffer_frames));
- if (ret != 0) {
- ALOGW("pcm_read failed %s", pcm_get_error(pcm));
- restart = true;
- continue;
- }
-
- pthread_mutex_lock(&in->lock);
- size_t frames_written = audio_vbuffer_write(&in->buffer, buffer, buffer_frames);
- pthread_mutex_unlock(&in->lock);
-
- if (frames_written != buffer_frames) {
- ALOGW("in_read_worker only could write %zu / %zu frames", frames_written, buffer_frames);
- }
- }
- if (buffer) {
- free(buffer);
- }
- return NULL;
-}
-
-static ssize_t in_read(struct audio_stream_in *stream, void* buffer,
- size_t bytes)
-{
- struct generic_stream_in *in = (struct generic_stream_in *)stream;
- struct generic_audio_device *adev = in->dev;
- const size_t frames = bytes / audio_stream_in_frame_size(stream);
- bool mic_mute = false;
- size_t read_bytes = 0;
-
- adev_get_mic_mute(&adev->device, &mic_mute);
- pthread_mutex_lock(&in->lock);
-
- if (in->worker_standby) {
- in->worker_standby = false;
- }
- pthread_cond_signal(&in->worker_wake);
-
- int64_t current_position;
- struct timespec current_time;
-
- get_current_input_position(in, ¤t_position, ¤t_time);
- if (in->standby) {
- in->standby = false;
- in->standby_exit_time = current_time;
- in->standby_frames_read = 0;
- }
-
- const int64_t frames_available = current_position - in->standby_position - in->standby_frames_read;
- assert(frames_available >= 0);
-
- const size_t frames_wait = ((uint64_t)frames_available > frames) ? 0 : frames - frames_available;
-
- int64_t sleep_time_us = frames_wait * 1000000LL /
- in_get_sample_rate(&stream->common);
-
- pthread_mutex_unlock(&in->lock);
-
- if (sleep_time_us > 0) {
- usleep(sleep_time_us);
- }
-
- pthread_mutex_lock(&in->lock);
- int read_frames = 0;
- if (in->standby) {
- ALOGW("Input put to sleep while read in progress");
- goto exit;
- }
- in->standby_frames_read += frames;
-
- if (popcount(in->req_config.channel_mask) == 1 &&
- in->pcm_config.channels == 2) {
- // Need to resample to mono
- if (in->stereo_to_mono_buf_size < bytes*2) {
- in->stereo_to_mono_buf = realloc(in->stereo_to_mono_buf,
- bytes*2);
- if (!in->stereo_to_mono_buf) {
- ALOGE("Failed to allocate stereo_to_mono_buff");
- goto exit;
- }
- }
-
- read_frames = audio_vbuffer_read(&in->buffer, in->stereo_to_mono_buf, frames);
-
- // Currently only pcm 16 is supported.
- uint16_t *src = (uint16_t *)in->stereo_to_mono_buf;
- uint16_t *dst = (uint16_t *)buffer;
- size_t i;
- // Resample stereo 16 to mono 16 by dropping one channel.
- // The stereo stream is interleaved L-R-L-R
- for (i = 0; i < frames; i++) {
- *dst = *src;
- src += 2;
- dst += 1;
- }
- } else {
- read_frames = audio_vbuffer_read(&in->buffer, buffer, frames);
- }
-
-exit:
- read_bytes = read_frames*audio_stream_in_frame_size(stream);
-
- if (mic_mute) {
- read_bytes = 0;
- }
-
- if (read_bytes < bytes) {
- memset (&((uint8_t *)buffer)[read_bytes], 0, bytes-read_bytes);
- }
-
- pthread_mutex_unlock(&in->lock);
-
- return bytes;
-}
-
-static uint32_t in_get_input_frames_lost(struct audio_stream_in *stream)
-{
- return 0;
-}
-
-static int in_get_capture_position(const struct audio_stream_in *stream,
- int64_t *frames, int64_t *time)
-{
- struct generic_stream_in *in = (struct generic_stream_in *)stream;
- pthread_mutex_lock(&in->lock);
- struct timespec current_time;
- get_current_input_position(in, frames, ¤t_time);
- *time = (current_time.tv_sec * 1000000000LL + current_time.tv_nsec);
- pthread_mutex_unlock(&in->lock);
- return 0;
-}
-
-static int in_get_active_microphones(const struct audio_stream_in *stream,
- struct audio_microphone_characteristic_t *mic_array,
- size_t *mic_count)
-{
- return adev_get_microphones(NULL, mic_array, mic_count);
-}
-
-static int in_add_audio_effect(const struct audio_stream *stream, effect_handle_t effect)
-{
- // in_add_audio_effect is a no op
- return 0;
-}
-
-static int in_remove_audio_effect(const struct audio_stream *stream, effect_handle_t effect)
-{
- // in_add_audio_effect is a no op
- return 0;
-}
-
-static int adev_open_output_stream(struct audio_hw_device *dev,
- audio_io_handle_t handle,
- audio_devices_t devices,
- audio_output_flags_t flags,
- struct audio_config *config,
- struct audio_stream_out **stream_out,
- const char *address __unused)
-{
- struct generic_audio_device *adev = (struct generic_audio_device *)dev;
- struct generic_stream_out *out;
- int ret = 0;
-
- if (refine_output_parameters(&config->sample_rate, &config->format, &config->channel_mask)) {
- ALOGE("Error opening output stream format %d, channel_mask %04x, sample_rate %u",
- config->format, config->channel_mask, config->sample_rate);
- ret = -EINVAL;
- goto error;
- }
-
- out = (struct generic_stream_out *)calloc(1, sizeof(struct generic_stream_out));
-
- if (!out)
- return -ENOMEM;
-
- out->stream.common.get_sample_rate = out_get_sample_rate;
- out->stream.common.set_sample_rate = out_set_sample_rate;
- out->stream.common.get_buffer_size = out_get_buffer_size;
- out->stream.common.get_channels = out_get_channels;
- out->stream.common.get_format = out_get_format;
- out->stream.common.set_format = out_set_format;
- out->stream.common.standby = out_standby;
- out->stream.common.dump = out_dump;
- out->stream.common.set_parameters = out_set_parameters;
- out->stream.common.get_parameters = out_get_parameters;
- out->stream.common.add_audio_effect = out_add_audio_effect;
- out->stream.common.remove_audio_effect = out_remove_audio_effect;
- out->stream.get_latency = out_get_latency;
- out->stream.set_volume = out_set_volume;
- out->stream.write = out_write;
- out->stream.get_render_position = out_get_render_position;
- out->stream.get_presentation_position = out_get_presentation_position;
- out->stream.get_next_write_timestamp = out_get_next_write_timestamp;
-
- out->handle = handle;
-
- pthread_mutex_init(&out->lock, (const pthread_mutexattr_t *) NULL);
- out->dev = adev;
- // Only 1 device is expected despite the argument being named 'devices'
- out->num_devices = 1;
- out->devices[0] = devices;
- memcpy(&out->req_config, config, sizeof(struct audio_config));
- memcpy(&out->pcm_config, &pcm_config_out, sizeof(struct pcm_config));
- out->pcm_config.rate = config->sample_rate;
- out->pcm_config.period_size = out->pcm_config.rate*OUT_PERIOD_MS/1000;
-
- out->standby = true;
- out->underrun_position = 0;
- out->underrun_time.tv_sec = 0;
- out->underrun_time.tv_nsec = 0;
- out->last_write_time_us = 0;
- out->frames_total_buffered = 0;
- out->frames_written = 0;
- out->frames_rendered = 0;
-
- ret = audio_vbuffer_init(&out->buffer,
- out->pcm_config.period_size*out->pcm_config.period_count,
- out->pcm_config.channels *
- pcm_format_to_bits(out->pcm_config.format) >> 3);
- if (ret == 0) {
- pthread_cond_init(&out->worker_wake, NULL);
- out->worker_standby = true;
- out->worker_exit = false;
- pthread_create(&out->worker_thread, NULL, out_write_worker, out);
-
- }
-
- pthread_mutex_lock(&adev->lock);
- list_add_tail(&adev->out_streams, &out->stream_node);
- pthread_mutex_unlock(&adev->lock);
-
- *stream_out = &out->stream;
-
-error:
-
- return ret;
-}
-
-// This must be called with adev->lock held.
-struct generic_stream_out *get_stream_out_by_io_handle_l(
- struct generic_audio_device *adev, audio_io_handle_t handle) {
- struct listnode *node;
-
- list_for_each(node, &adev->out_streams) {
- struct generic_stream_out *out = node_to_item(
- node, struct generic_stream_out, stream_node);
- if (out->handle == handle) {
- return out;
- }
- }
- return NULL;
-}
-
-static void adev_close_output_stream(struct audio_hw_device *dev,
- struct audio_stream_out *stream)
-{
- struct generic_stream_out *out = (struct generic_stream_out *)stream;
- pthread_mutex_lock(&out->lock);
- do_out_standby(out);
-
- out->worker_exit = true;
- pthread_cond_signal(&out->worker_wake);
- pthread_mutex_unlock(&out->lock);
-
- pthread_join(out->worker_thread, NULL);
- pthread_mutex_destroy(&out->lock);
- audio_vbuffer_destroy(&out->buffer);
-
- struct generic_audio_device *adev = (struct generic_audio_device *) dev;
- pthread_mutex_lock(&adev->lock);
- list_remove(&out->stream_node);
- pthread_mutex_unlock(&adev->lock);
- free(stream);
-}
-
-static int adev_set_parameters(struct audio_hw_device *dev, const char *kvpairs)
-{
- return 0;
-}
-
-static char * adev_get_parameters(const struct audio_hw_device *dev,
- const char *keys)
-{
- return strdup("");
-}
-
-static int adev_get_audio_port(struct audio_hw_device *dev,
- struct audio_port *port)
-{
- return 0;
-}
-
-static int adev_init_check(const struct audio_hw_device *dev)
-{
- return 0;
-}
-
-static int adev_set_voice_volume(struct audio_hw_device *dev, float volume)
-{
- // adev_set_voice_volume is a no op (simulates phones)
- return 0;
-}
-
-static int adev_set_audio_port_config(struct audio_hw_device *dev,
- const struct audio_port_config *config) {
- return 0;
-}
-
-static int adev_set_master_volume(struct audio_hw_device *dev, float volume)
-{
- return -ENOSYS;
-}
-
-static int adev_get_master_volume(struct audio_hw_device *dev, float *volume)
-{
- return -ENOSYS;
-}
-
-static int adev_set_master_mute(struct audio_hw_device *dev, bool muted)
-{
- return -ENOSYS;
-}
-
-static int adev_get_master_mute(struct audio_hw_device *dev, bool *muted)
-{
- return -ENOSYS;
-}
-
-static int adev_set_mode(struct audio_hw_device *dev, audio_mode_t mode)
-{
- // adev_set_mode is a no op (simulates phones)
- return 0;
-}
-
-static int adev_set_mic_mute(struct audio_hw_device *dev, bool state)
-{
- struct generic_audio_device *adev = (struct generic_audio_device *)dev;
- pthread_mutex_lock(&adev->lock);
- adev->mic_mute = state;
- pthread_mutex_unlock(&adev->lock);
- return 0;
-}
-
-static int adev_get_mic_mute(const struct audio_hw_device *dev, bool *state)
-{
- struct generic_audio_device *adev = (struct generic_audio_device *)dev;
- pthread_mutex_lock(&adev->lock);
- *state = adev->mic_mute;
- pthread_mutex_unlock(&adev->lock);
- return 0;
-}
-
-
-static size_t adev_get_input_buffer_size(const struct audio_hw_device *dev,
- const struct audio_config *config)
-{
- return get_input_buffer_size(config->sample_rate, config->format, config->channel_mask);
-}
-
-// This must be called with adev->lock held.
-struct generic_stream_in *get_stream_in_by_io_handle_l(
- struct generic_audio_device *adev, audio_io_handle_t handle) {
- struct listnode *node;
-
- list_for_each(node, &adev->in_streams) {
- struct generic_stream_in *in = node_to_item(
- node, struct generic_stream_in, stream_node);
- if (in->handle == handle) {
- return in;
- }
- }
- return NULL;
-}
-
-static void adev_close_input_stream(struct audio_hw_device *dev,
- struct audio_stream_in *stream)
-{
- struct generic_stream_in *in = (struct generic_stream_in *)stream;
- pthread_mutex_lock(&in->lock);
- do_in_standby(in);
-
- in->worker_exit = true;
- pthread_cond_signal(&in->worker_wake);
- pthread_mutex_unlock(&in->lock);
- pthread_join(in->worker_thread, NULL);
-
- if (in->stereo_to_mono_buf != NULL) {
- free(in->stereo_to_mono_buf);
- in->stereo_to_mono_buf_size = 0;
- }
-
- pthread_mutex_destroy(&in->lock);
- audio_vbuffer_destroy(&in->buffer);
-
- struct generic_audio_device *adev = (struct generic_audio_device *) dev;
- pthread_mutex_lock(&adev->lock);
- list_remove(&in->stream_node);
- pthread_mutex_unlock(&adev->lock);
- free(stream);
-}
-
-
-static int adev_open_input_stream(struct audio_hw_device *dev,
- audio_io_handle_t handle,
- audio_devices_t devices,
- struct audio_config *config,
- struct audio_stream_in **stream_in,
- audio_input_flags_t flags __unused,
- const char *address __unused,
- audio_source_t source __unused)
-{
- struct generic_audio_device *adev = (struct generic_audio_device *)dev;
- struct generic_stream_in *in;
- int ret = 0;
- uint32_t orig_sample_rate = config->sample_rate;
- audio_format_t orig_audio_format = config->format;
- audio_channel_mask_t orig_channel_mask = config->channel_mask;
- if (refine_input_parameters(&config->sample_rate, &config->format, &config->channel_mask)) {
- ALOGE("Error opening input stream format %d, channel_mask %04x, sample_rate %u",
- orig_audio_format, orig_channel_mask, orig_sample_rate);
- ret = -EINVAL;
- goto error;
- }
-
- in = (struct generic_stream_in *)calloc(1, sizeof(struct generic_stream_in));
- if (!in) {
- ret = -ENOMEM;
- goto error;
- }
-
- in->stream.common.get_sample_rate = in_get_sample_rate;
- in->stream.common.set_sample_rate = in_set_sample_rate; // no op
- in->stream.common.get_buffer_size = in_get_buffer_size;
- in->stream.common.get_channels = in_get_channels;
- in->stream.common.get_format = in_get_format;
- in->stream.common.set_format = in_set_format; // no op
- in->stream.common.standby = in_standby;
- in->stream.common.dump = in_dump;
- in->stream.common.set_parameters = in_set_parameters;
- in->stream.common.get_parameters = in_get_parameters;
- in->stream.common.add_audio_effect = in_add_audio_effect; // no op
- in->stream.common.remove_audio_effect = in_remove_audio_effect; // no op
- in->stream.set_gain = in_set_gain; // no op
- in->stream.read = in_read;
- in->stream.get_input_frames_lost = in_get_input_frames_lost; // no op
- in->stream.get_capture_position = in_get_capture_position;
- in->stream.get_active_microphones = in_get_active_microphones;
-
- pthread_mutex_init(&in->lock, (const pthread_mutexattr_t *) NULL);
- in->dev = adev;
- in->device = devices;
- memcpy(&in->req_config, config, sizeof(struct audio_config));
- memcpy(&in->pcm_config, &pcm_config_in, sizeof(struct pcm_config));
- in->pcm_config.rate = config->sample_rate;
- in->pcm_config.period_size = in->pcm_config.rate*IN_PERIOD_MS/1000;
-
- in->stereo_to_mono_buf = NULL;
- in->stereo_to_mono_buf_size = 0;
-
- in->standby = true;
- in->standby_position = 0;
- in->standby_exit_time.tv_sec = 0;
- in->standby_exit_time.tv_nsec = 0;
- in->standby_frames_read = 0;
-
- ret = audio_vbuffer_init(&in->buffer,
- in->pcm_config.period_size*in->pcm_config.period_count,
- in->pcm_config.channels *
- pcm_format_to_bits(in->pcm_config.format) >> 3);
- if (ret == 0) {
- pthread_cond_init(&in->worker_wake, NULL);
- in->worker_standby = true;
- in->worker_exit = false;
- pthread_create(&in->worker_thread, NULL, in_read_worker, in);
- }
- in->handle = handle;
-
- pthread_mutex_lock(&adev->lock);
- list_add_tail(&adev->in_streams, &in->stream_node);
- pthread_mutex_unlock(&adev->lock);
-
- *stream_in = &in->stream;
-
-error:
- return ret;
-}
-
-
-static int adev_dump(const audio_hw_device_t *dev, int fd)
-{
- return 0;
-}
-
-static int adev_get_microphones(const audio_hw_device_t *dev,
- struct audio_microphone_characteristic_t *mic_array,
- size_t *mic_count)
-{
- if (mic_count == NULL) {
- return -ENOSYS;
- }
-
- if (*mic_count == 0) {
- *mic_count = 1;
- return 0;
- }
-
- if (mic_array == NULL) {
- return -ENOSYS;
- }
-
- strncpy(mic_array->device_id, "mic_goldfish", AUDIO_MICROPHONE_ID_MAX_LEN - 1);
- mic_array->device = AUDIO_DEVICE_IN_BUILTIN_MIC;
- strncpy(mic_array->address, AUDIO_BOTTOM_MICROPHONE_ADDRESS,
- AUDIO_DEVICE_MAX_ADDRESS_LEN - 1);
- memset(mic_array->channel_mapping, AUDIO_MICROPHONE_CHANNEL_MAPPING_UNUSED,
- sizeof(mic_array->channel_mapping));
- mic_array->location = AUDIO_MICROPHONE_LOCATION_UNKNOWN;
- mic_array->group = 0;
- mic_array->index_in_the_group = 0;
- mic_array->sensitivity = AUDIO_MICROPHONE_SENSITIVITY_UNKNOWN;
- mic_array->max_spl = AUDIO_MICROPHONE_SPL_UNKNOWN;
- mic_array->min_spl = AUDIO_MICROPHONE_SPL_UNKNOWN;
- mic_array->directionality = AUDIO_MICROPHONE_DIRECTIONALITY_UNKNOWN;
- mic_array->num_frequency_responses = 0;
- mic_array->geometric_location.x = AUDIO_MICROPHONE_COORDINATE_UNKNOWN;
- mic_array->geometric_location.y = AUDIO_MICROPHONE_COORDINATE_UNKNOWN;
- mic_array->geometric_location.z = AUDIO_MICROPHONE_COORDINATE_UNKNOWN;
- mic_array->orientation.x = AUDIO_MICROPHONE_COORDINATE_UNKNOWN;
- mic_array->orientation.y = AUDIO_MICROPHONE_COORDINATE_UNKNOWN;
- mic_array->orientation.z = AUDIO_MICROPHONE_COORDINATE_UNKNOWN;
-
- *mic_count = 1;
- return 0;
-}
-
-static int adev_create_audio_patch(struct audio_hw_device *dev,
- unsigned int num_sources,
- const struct audio_port_config *sources,
- unsigned int num_sinks,
- const struct audio_port_config *sinks,
- audio_patch_handle_t *handle) {
- if (num_sources != 1 || num_sinks == 0 || num_sinks > AUDIO_PATCH_PORTS_MAX) {
- return -EINVAL;
- }
-
- if (sources[0].type == AUDIO_PORT_TYPE_DEVICE) {
- // If source is a device, the number of sinks should be 1.
- if (num_sinks != 1 || sinks[0].type != AUDIO_PORT_TYPE_MIX) {
- return -EINVAL;
- }
- } else if (sources[0].type == AUDIO_PORT_TYPE_MIX) {
- // If source is a mix, all sinks should be device.
- for (unsigned int i = 0; i < num_sinks; i++) {
- if (sinks[i].type != AUDIO_PORT_TYPE_DEVICE) {
- ALOGE("%s() invalid sink type %#x for mix source", __func__, sinks[i].type);
- return -EINVAL;
- }
- }
- } else {
- // All other cases are invalid.
- return -EINVAL;
- }
-
- struct generic_audio_device* adev = (struct generic_audio_device*) dev;
- int ret = 0;
- bool generatedPatchHandle = false;
- pthread_mutex_lock(&adev->lock);
- if (*handle == AUDIO_PATCH_HANDLE_NONE) {
- *handle = ++adev->next_patch_handle;
- generatedPatchHandle = true;
- }
-
- // Only handle patches for mix->devices and device->mix case.
- if (sources[0].type == AUDIO_PORT_TYPE_DEVICE) {
- struct generic_stream_in *in =
- get_stream_in_by_io_handle_l(adev, sinks[0].ext.mix.handle);
- if (in == NULL) {
- ALOGE("%s()can not find stream with handle(%d)", __func__, sources[0].ext.mix.handle);
- ret = -EINVAL;
- goto error;
- }
-
- // Check if the patch handle match the recorded one if a valid patch handle is passed.
- if (!generatedPatchHandle && in->patch_handle != *handle) {
- ALOGE("%s() the patch handle(%d) does not match recorded one(%d) for stream "
- "with handle(%d) when creating audio patch for device->mix",
- __func__, *handle, in->patch_handle, in->handle);
- ret = -EINVAL;
- goto error;
- }
- pthread_mutex_lock(&in->lock);
- in->device = sources[0].ext.device.type;
- pthread_mutex_unlock(&in->lock);
- in->patch_handle = *handle;
- } else {
- struct generic_stream_out *out =
- get_stream_out_by_io_handle_l(adev, sources[0].ext.mix.handle);
- if (out == NULL) {
- ALOGE("%s()can not find stream with handle(%d)", __func__, sources[0].ext.mix.handle);
- ret = -EINVAL;
- goto error;
- }
-
- // Check if the patch handle match the recorded one if a valid patch handle is passed.
- if (!generatedPatchHandle && out->patch_handle != *handle) {
- ALOGE("%s() the patch handle(%d) does not match recorded one(%d) for stream "
- "with handle(%d) when creating audio patch for mix->device",
- __func__, *handle, out->patch_handle, out->handle);
- ret = -EINVAL;
- pthread_mutex_unlock(&out->lock);
- goto error;
- }
- pthread_mutex_lock(&out->lock);
- for (out->num_devices = 0; out->num_devices < num_sinks; out->num_devices++) {
- out->devices[out->num_devices] = sinks[out->num_devices].ext.device.type;
- }
- pthread_mutex_unlock(&out->lock);
- out->patch_handle = *handle;
- }
-
-error:
- if (ret != 0 && generatedPatchHandle) {
- *handle = AUDIO_PATCH_HANDLE_NONE;
- }
- pthread_mutex_unlock(&adev->lock);
- return 0;
-}
-
-// This must be called with adev->lock held.
-struct generic_stream_out *get_stream_out_by_patch_handle_l(
- struct generic_audio_device *adev, audio_patch_handle_t patch_handle) {
- struct listnode *node;
-
- list_for_each(node, &adev->out_streams) {
- struct generic_stream_out *out = node_to_item(
- node, struct generic_stream_out, stream_node);
- if (out->patch_handle == patch_handle) {
- return out;
- }
- }
- return NULL;
-}
-
-// This must be called with adev->lock held.
-struct generic_stream_in *get_stream_in_by_patch_handle_l(
- struct generic_audio_device *adev, audio_patch_handle_t patch_handle) {
- struct listnode *node;
-
- list_for_each(node, &adev->in_streams) {
- struct generic_stream_in *in = node_to_item(
- node, struct generic_stream_in, stream_node);
- if (in->patch_handle == patch_handle) {
- return in;
- }
- }
- return NULL;
-}
-
-static int adev_release_audio_patch(struct audio_hw_device *dev,
- audio_patch_handle_t patch_handle) {
- struct generic_audio_device *adev = (struct generic_audio_device *) dev;
-
- pthread_mutex_lock(&adev->lock);
- struct generic_stream_out *out = get_stream_out_by_patch_handle_l(adev, patch_handle);
- if (out != NULL) {
- pthread_mutex_lock(&out->lock);
- out->num_devices = 0;
- memset(out->devices, 0, sizeof(out->devices));
- pthread_mutex_unlock(&out->lock);
- out->patch_handle = AUDIO_PATCH_HANDLE_NONE;
- pthread_mutex_unlock(&adev->lock);
- return 0;
- }
- struct generic_stream_in *in = get_stream_in_by_patch_handle_l(adev, patch_handle);
- if (in != NULL) {
- pthread_mutex_lock(&in->lock);
- in->device = AUDIO_DEVICE_NONE;
- pthread_mutex_unlock(&in->lock);
- in->patch_handle = AUDIO_PATCH_HANDLE_NONE;
- pthread_mutex_unlock(&adev->lock);
- return 0;
- }
-
- pthread_mutex_unlock(&adev->lock);
- ALOGW("%s() cannot find stream for patch handle: %d", __func__, patch_handle);
- return -EINVAL;
-}
-
-static int adev_close(hw_device_t *dev)
-{
- struct generic_audio_device *adev = (struct generic_audio_device *)dev;
- int ret = 0;
- if (!adev)
- return 0;
-
- pthread_mutex_lock(&adev_init_lock);
-
- if (audio_device_ref_count == 0) {
- ALOGE("adev_close called when ref_count 0");
- ret = -EINVAL;
- goto error;
- }
-
- if ((--audio_device_ref_count) == 0) {
- if (adev->mixer) {
- mixer_close(adev->mixer);
- }
- free(adev);
- }
-
-error:
- pthread_mutex_unlock(&adev_init_lock);
- return ret;
-}
-
-static int adev_open(const hw_module_t* module, const char* name,
- hw_device_t** device)
-{
- static struct generic_audio_device *adev;
-
- if (strcmp(name, AUDIO_HARDWARE_INTERFACE) != 0)
- return -EINVAL;
-
- pthread_mutex_lock(&adev_init_lock);
- if (audio_device_ref_count != 0) {
- *device = &adev->device.common;
- audio_device_ref_count++;
- ALOGV("%s: returning existing instance of adev", __func__);
- ALOGV("%s: exit", __func__);
- goto unlock;
- }
- adev = calloc(1, sizeof(struct generic_audio_device));
-
- pthread_mutex_init(&adev->lock, (const pthread_mutexattr_t *) NULL);
-
- adev->device.common.tag = HARDWARE_DEVICE_TAG;
- adev->device.common.version = AUDIO_DEVICE_API_VERSION_3_0;
- adev->device.common.module = (struct hw_module_t *) module;
- adev->device.common.close = adev_close;
-
- adev->device.init_check = adev_init_check; // no op
- adev->device.set_voice_volume = adev_set_voice_volume; // no op
- adev->device.set_master_volume = adev_set_master_volume; // no op
- adev->device.get_master_volume = adev_get_master_volume; // no op
- adev->device.set_master_mute = adev_set_master_mute; // no op
- adev->device.get_master_mute = adev_get_master_mute; // no op
- adev->device.set_mode = adev_set_mode; // no op
- adev->device.set_mic_mute = adev_set_mic_mute;
- adev->device.get_mic_mute = adev_get_mic_mute;
- adev->device.set_parameters = adev_set_parameters; // no op
- adev->device.get_parameters = adev_get_parameters; // no op
- adev->device.get_audio_port = adev_get_audio_port; // no op
- adev->device.set_audio_port_config = adev_set_audio_port_config; // no op
- adev->device.get_input_buffer_size = adev_get_input_buffer_size;
- adev->device.open_output_stream = adev_open_output_stream;
- adev->device.close_output_stream = adev_close_output_stream;
- adev->device.open_input_stream = adev_open_input_stream;
- adev->device.close_input_stream = adev_close_input_stream;
- adev->device.dump = adev_dump;
- adev->device.get_microphones = adev_get_microphones;
- adev->device.create_audio_patch = adev_create_audio_patch;
- adev->device.release_audio_patch = adev_release_audio_patch;
-
- *device = &adev->device.common;
-
- adev->next_patch_handle = AUDIO_PATCH_HANDLE_NONE;
- list_init(&adev->out_streams);
- list_init(&adev->in_streams);
-
- adev->mixer = mixer_open(PCM_CARD);
- struct mixer_ctl *ctl;
-
- // Set default mixer ctls
- // Enable channels and set volume
- for (int i = 0; i < (int)mixer_get_num_ctls(adev->mixer); i++) {
- ctl = mixer_get_ctl(adev->mixer, i);
- ALOGD("mixer %d name %s", i, mixer_ctl_get_name(ctl));
- if (!strcmp(mixer_ctl_get_name(ctl), "Master Playback Volume") ||
- !strcmp(mixer_ctl_get_name(ctl), "Capture Volume")) {
- for (int z = 0; z < (int)mixer_ctl_get_num_values(ctl); z++) {
- ALOGD("set ctl %d to %d", z, 100);
- mixer_ctl_set_percent(ctl, z, 100);
- }
- continue;
- }
- if (!strcmp(mixer_ctl_get_name(ctl), "Master Playback Switch") ||
- !strcmp(mixer_ctl_get_name(ctl), "Capture Switch")) {
- for (int z = 0; z < (int)mixer_ctl_get_num_values(ctl); z++) {
- ALOGD("set ctl %d to %d", z, 1);
- mixer_ctl_set_value(ctl, z, 1);
- }
- continue;
- }
- }
-
- audio_device_ref_count++;
-
-unlock:
- pthread_mutex_unlock(&adev_init_lock);
- return 0;
-}
-
-static struct hw_module_methods_t hal_module_methods = {
- .open = adev_open,
-};
-
-struct audio_module HAL_MODULE_INFO_SYM = {
- .common = {
- .tag = HARDWARE_MODULE_TAG,
- .module_api_version = AUDIO_MODULE_API_VERSION_0_1,
- .hal_api_version = HARDWARE_HAL_API_VERSION,
- .id = AUDIO_HARDWARE_MODULE_ID,
- .name = "Generic audio HW HAL",
- .author = "The Android Open Source Project",
- .methods = &hal_module_methods,
- },
-};
diff --git a/guest/hals/bt/OWNERS b/guest/hals/bt/OWNERS
index e8a4a00..b4bb5dd 100644
--- a/guest/hals/bt/OWNERS
+++ b/guest/hals/bt/OWNERS
@@ -1,2 +1,3 @@
-include platform/system/bt:/OWNERS
+include device/google/cuttlefish:/OWNERS
+include platform/packages/modules/Bluetooth:/OWNERS
[email protected]
\ No newline at end of file
diff --git a/guest/hals/bt/remote/Android.bp b/guest/hals/bt/remote/Android.bp
index 5ad483a..512bee5 100644
--- a/guest/hals/bt/remote/Android.bp
+++ b/guest/hals/bt/remote/Android.bp
@@ -25,7 +25,6 @@
],
static_libs: [
"libbt-rootcanal",
- "libbt-rootcanal-types",
"async_fd_watcher",
],
init_rc: ["[email protected]"],
diff --git a/guest/hals/bt/remote/remote_bluetooth.cpp b/guest/hals/bt/remote/remote_bluetooth.cpp
index dbcede3..d041af7 100644
--- a/guest/hals/bt/remote/remote_bluetooth.cpp
+++ b/guest/hals/bt/remote/remote_bluetooth.cpp
@@ -125,7 +125,7 @@
CHECK(death_recipient_->getHasDied())
<< "Error sending init callback, but no death notification.";
}
- h4_ = test_vendor_lib::H4Packetizer(
+ h4_ = rootcanal::H4Packetizer(
fd_,
[](const std::vector<uint8_t>& /* raw_command */) {
LOG_ALWAYS_FATAL("Unexpected command!");
@@ -159,26 +159,26 @@
}
Return<void> BluetoothHci::sendHciCommand(const hidl_vec<uint8_t>& packet) {
- send(test_vendor_lib::PacketType::COMMAND, packet);
+ send(rootcanal::PacketType::COMMAND, packet);
return Void();
}
Return<void> BluetoothHci::sendAclData(const hidl_vec<uint8_t>& packet) {
- send(test_vendor_lib::PacketType::ACL, packet);
+ send(rootcanal::PacketType::ACL, packet);
return Void();
}
Return<void> BluetoothHci::sendScoData(const hidl_vec<uint8_t>& packet) {
- send(test_vendor_lib::PacketType::SCO, packet);
+ send(rootcanal::PacketType::SCO, packet);
return Void();
}
Return<void> BluetoothHci::sendIsoData(const hidl_vec<uint8_t>& packet) {
- send(test_vendor_lib::PacketType::ISO, packet);
+ send(rootcanal::PacketType::ISO, packet);
return Void();
}
-void BluetoothHci::send(test_vendor_lib::PacketType type,
+void BluetoothHci::send(rootcanal::PacketType type,
const ::android::hardware::hidl_vec<uint8_t>& v) {
h4_.Send(static_cast<uint8_t>(type), v.data(), v.size());
}
diff --git a/guest/hals/bt/remote/remote_bluetooth.h b/guest/hals/bt/remote/remote_bluetooth.h
index a7aa24c..ade39d0 100644
--- a/guest/hals/bt/remote/remote_bluetooth.h
+++ b/guest/hals/bt/remote/remote_bluetooth.h
@@ -22,7 +22,7 @@
#include <hidl/MQDescriptor.h>
#include <string>
#include "async_fd_watcher.h"
-#include "model/devices/h4_packetizer.h"
+#include "model/hci/h4_packetizer.h"
namespace android {
namespace hardware {
@@ -68,13 +68,13 @@
::android::sp<V1_0::IBluetoothHciCallbacks> cb_ = nullptr;
::android::sp<V1_1::IBluetoothHciCallbacks> cb_1_1_ = nullptr;
- test_vendor_lib::H4Packetizer h4_{fd_,
- [](const std::vector<uint8_t>&) {},
- [](const std::vector<uint8_t>&) {},
- [](const std::vector<uint8_t>&) {},
- [](const std::vector<uint8_t>&) {},
- [](const std::vector<uint8_t>&) {},
- [] {}};
+ rootcanal::H4Packetizer h4_{fd_,
+ [](const std::vector<uint8_t>&) {},
+ [](const std::vector<uint8_t>&) {},
+ [](const std::vector<uint8_t>&) {},
+ [](const std::vector<uint8_t>&) {},
+ [](const std::vector<uint8_t>&) {},
+ [] {}};
::android::hardware::Return<void> initialize_impl(
const sp<V1_0::IBluetoothHciCallbacks>& cb,
@@ -88,7 +88,7 @@
::android::hardware::bluetooth::async::AsyncFdWatcher fd_watcher_;
- void send(test_vendor_lib::PacketType type,
+ void send(rootcanal::PacketType type,
const ::android::hardware::hidl_vec<uint8_t>& packet);
};
diff --git a/guest/hals/camera/vsock_frame_provider.cpp b/guest/hals/camera/vsock_frame_provider.cpp
index 435d5f9..baa4c52 100644
--- a/guest/hals/camera/vsock_frame_provider.cpp
+++ b/guest/hals/camera/vsock_frame_provider.cpp
@@ -22,6 +22,16 @@
namespace cuttlefish {
+namespace {
+bool writeJsonEventMessage(
+ std::shared_ptr<cuttlefish::VsockConnection> connection,
+ const std::string& message) {
+ Json::Value json_message;
+ json_message["event"] = message;
+ return connection && connection->WriteMessage(json_message);
+}
+} // namespace
+
VsockFrameProvider::~VsockFrameProvider() { stop(); }
void VsockFrameProvider::start(
@@ -30,6 +40,7 @@
stop();
running_ = true;
connection_ = connection;
+ writeJsonEventMessage(connection, "VIRTUAL_DEVICE_START_CAMERA_SESSION");
reader_thread_ =
std::thread([this, width, height] { VsockReadLoop(width, height); });
}
@@ -40,6 +51,7 @@
if (reader_thread_.joinable()) {
reader_thread_.join();
}
+ writeJsonEventMessage(connection_, "VIRTUAL_DEVICE_STOP_CAMERA_SESSION");
connection_ = nullptr;
}
@@ -53,11 +65,7 @@
void VsockFrameProvider::requestJpeg() {
jpeg_pending_ = true;
- Json::Value message;
- message["event"] = "VIRTUAL_DEVICE_CAPTURE_IMAGE";
- if (connection_) {
- connection_->WriteMessage(message);
- }
+ writeJsonEventMessage(connection_, "VIRTUAL_DEVICE_CAPTURE_IMAGE");
}
void VsockFrameProvider::cancelJpegRequest() { jpeg_pending_ = false; }
diff --git a/guest/hals/confirmationui/.clang-format b/guest/hals/confirmationui/.clang-format
new file mode 100644
index 0000000..b0dc94c
--- /dev/null
+++ b/guest/hals/confirmationui/.clang-format
@@ -0,0 +1,10 @@
+BasedOnStyle: LLVM
+IndentWidth: 4
+UseTab: Never
+BreakBeforeBraces: Attach
+AllowShortFunctionsOnASingleLine: Inline
+AllowShortIfStatementsOnASingleLine: true
+IndentCaseLabels: false
+ColumnLimit: 100
+PointerBindsToType: true
+SpacesBeforeTrailingComments: 2
diff --git a/guest/hals/confirmationui/Android.bp b/guest/hals/confirmationui/Android.bp
new file mode 100644
index 0000000..cb9318a
--- /dev/null
+++ b/guest/hals/confirmationui/Android.bp
@@ -0,0 +1,89 @@
+// Copyright (C) 2021 The Android Open-Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+// WARNING: Everything listed here will be built on ALL platforms,
+// including x86, the emulator, and the SDK. Modules must be uniquely
+// named (liblights.panda), and must build everywhere, or limit themselves
+// to only building on ARM if they include assembly. Individual makefiles
+// are responsible for having their own logic, for fine-grained control.
+
+package {
+ default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+cc_binary {
+ name: "[email protected]",
+ defaults: ["hidl_defaults", "cuttlefish_guest_only"],
+ relative_install_path: "hw",
+ vendor: true,
+ shared_libs: [
+ "[email protected]",
+ "[email protected]",
+ "libbase",
+ "libhidlbase",
+ "libutils",
+ ],
+ static_libs: [
+ "libcuttlefish_confui",
+ ],
+
+ init_rc: ["[email protected]"],
+
+ vintf_fragments: ["[email protected]"],
+
+ srcs: [
+ "service.cpp",
+ ],
+
+ cflags: [
+ "-Wall",
+ "-Werror",
+ "-DTEEUI_USE_STD_VECTOR",
+ ],
+}
+
+cc_library {
+ name: "[email protected]",
+ defaults: ["hidl_defaults", "cuttlefish_guest_only"],
+ vendor: true,
+ shared_libs: [
+ "[email protected]",
+ "[email protected]",
+ "libbase",
+ "libcutils",
+ "libdmabufheap",
+ "libhidlbase",
+ "libteeui_hal_support",
+ "libtrusty",
+ "libutils",
+ ],
+
+ export_include_dirs: ["include"],
+
+ srcs: [
+ "TrustyConfirmationUI.cpp",
+ "guest_session.cpp",
+ ],
+ static_libs: [
+ "libcuttlefish_confui",
+ "libcuttlefish_fs",
+ ],
+ cflags: [
+ "-Wall",
+ "-Werror",
+ "-DTEEUI_USE_STD_VECTOR",
+ ],
+}
+
diff --git a/guest/hals/confirmationui/README b/guest/hals/confirmationui/README
new file mode 100644
index 0000000..45d4e76
--- /dev/null
+++ b/guest/hals/confirmationui/README
@@ -0,0 +1,20 @@
+## Secure UI Architecture
+
+To implement confirmationui a secure UI architecture is required. This entails a way
+to display the confirmation dialog driven by a reduced trusted computing base, typically
+a trusted execution environment (TEE), without having to rely on Linux and the Android
+system for integrity and authenticity of input events. This implementation provides
+neither. But it provides most of the functionlity required to run a full Android Protected
+Confirmation feature when integrated into a secure UI architecture.
+
+## Secure input (NotSoSecureInput)
+
+This implementation does not provide any security guaranties.
+The input method (NotSoSecureInput) runs a cryptographic protocols that is
+sufficiently secure IFF the end point is implemented on a trustworthy
+secure input device. But since the endpoint is currently in the HAL
+service itself this implementation is not secure.
+
+NOTE that a secure input device end point needs a good source of entropy
+for generating nonces. The current implementation (NotSoSecureInput.cpp#generateNonce)
+uses a constant nonce.
\ No newline at end of file
diff --git a/guest/hals/confirmationui/TrustyConfirmationUI.cpp b/guest/hals/confirmationui/TrustyConfirmationUI.cpp
new file mode 100644
index 0000000..7854332
--- /dev/null
+++ b/guest/hals/confirmationui/TrustyConfirmationUI.cpp
@@ -0,0 +1,248 @@
+/*
+ *
+ * Copyright 2019, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "TrustyConfirmationUI.h"
+
+#include <cutils/properties.h>
+
+namespace android {
+namespace hardware {
+namespace confirmationui {
+namespace V1_0 {
+namespace implementation {
+
+using ::teeui::MsgString;
+using ::teeui::MsgVector;
+using ::android::hardware::keymaster::V4_0::HardwareAuthToken;
+using TeeuiRc = ::teeui::ResponseCode;
+
+namespace {
+teeui::UIOption convertUIOption(UIOption uio) {
+ static_assert(uint32_t(UIOption::AccessibilityInverted) ==
+ uint32_t(teeui::UIOption::AccessibilityInverted) &&
+ uint32_t(UIOption::AccessibilityMagnified) ==
+ uint32_t(teeui::UIOption::AccessibilityMagnified),
+ "teeui::UIOPtion and ::android::hardware::confirmationui::V1_0::UIOption "
+ "are out of sync");
+ return teeui::UIOption(uio);
+}
+
+inline MsgString hidl2MsgString(const hidl_string& s) {
+ return {s.c_str(), s.c_str() + s.size()};
+}
+template <typename T> inline MsgVector<T> hidl2MsgVector(const hidl_vec<T>& v) {
+ return {v};
+}
+
+inline MsgVector<teeui::UIOption> hidl2MsgVector(const hidl_vec<UIOption>& v) {
+ MsgVector<teeui::UIOption> result(v.size());
+ for (unsigned int i = 0; i < v.size(); ++i) {
+ result[i] = convertUIOption(v[i]);
+ }
+ return result;
+}
+} // namespace
+
+cuttlefish::SharedFD TrustyConfirmationUI::ConnectToHost() {
+ using namespace std::chrono_literals;
+ while (true) {
+ auto host_fd = cuttlefish::SharedFD::VsockClient(2, host_vsock_port_, SOCK_STREAM);
+ if (host_fd->IsOpen()) {
+ ConfUiLog(INFO) << "Client connection is established";
+ return host_fd;
+ }
+ ConfUiLog(INFO) << "host service is not on. Sleep for 500 ms";
+ std::this_thread::sleep_for(500ms);
+ }
+}
+
+TrustyConfirmationUI::TrustyConfirmationUI()
+ : listener_state_(ListenerState::None),
+ prompt_result_(ResponseCode::Ignored), host_vsock_port_{static_cast<int>(property_get_int64(
+ "ro.boot.vsock_confirmationui_port", 7700))},
+ current_session_id_{10} {
+ ConfUiLog(INFO) << "Connecting to Confirmation UI host listening on port " << host_vsock_port_;
+ host_fd_ = ConnectToHost();
+ auto fetching_cmd = [this]() { HostMessageFetcherLoop(); };
+ if (host_fd_->IsOpen()) {
+ host_cmd_fetcher_thread_ = std::thread(fetching_cmd);
+ }
+}
+
+TrustyConfirmationUI::~TrustyConfirmationUI() {
+ if (host_fd_->IsOpen()) {
+ host_fd_->Close();
+ }
+ if (host_cmd_fetcher_thread_.joinable()) {
+ host_cmd_fetcher_thread_.join();
+ }
+
+ if (listener_state_ != ListenerState::None) {
+ callback_thread_.join();
+ }
+}
+
+void TrustyConfirmationUI::HostMessageFetcherLoop() {
+ while (true) {
+ if (!host_fd_->IsOpen()) {
+ // this happens when TrustyConfirmationUI is destroyed
+ ConfUiLog(ERROR) << "host_fd_ is not open";
+ return;
+ }
+ auto msg = cuttlefish::confui::RecvConfUiMsg(host_fd_);
+ if (!msg) {
+ // socket is broken for now
+ return;
+ }
+ {
+ std::unique_lock<std::mutex> lk(current_session_lock_);
+ if (!current_session_ || msg->GetSessionId() != current_session_->GetSessionId()) {
+ if (!current_session_) {
+ ConfUiLog(ERROR) << "msg is received but session is null";
+ continue;
+ }
+ ConfUiLog(ERROR) << "session id mismatch, so ignored"
+ << "Received for " << msg->GetSessionId()
+ << " but currently running " << current_session_->GetSessionId();
+ continue;
+ }
+ current_session_->Push(std::move(msg));
+ }
+ listener_state_condv_.notify_all();
+ }
+}
+
+void TrustyConfirmationUI::RunSession(sp<IConfirmationResultCallback> resultCB,
+ hidl_string promptText, hidl_vec<uint8_t> extraData,
+ hidl_string locale, hidl_vec<UIOption> uiOptions) {
+ cuttlefish::SharedFD fd = host_fd_;
+ // ownership of the fd is passed to GuestSession
+ {
+ std::unique_lock<std::mutex> lk(current_session_lock_);
+ current_session_ = std::make_unique<GuestSession>(
+ current_session_id_, listener_state_, listener_state_lock_, listener_state_condv_, fd,
+ hidl2MsgString(promptText), hidl2MsgVector(extraData), hidl2MsgString(locale),
+ hidl2MsgVector(uiOptions));
+ }
+
+ auto [rc, msg, token] = current_session_->PromptUserConfirmation();
+
+ std::unique_lock<std::mutex> lock(listener_state_lock_); // for listener_state_
+ bool do_callback = (listener_state_ == ListenerState::Interactive ||
+ listener_state_ == ListenerState::SetupDone) &&
+ resultCB;
+ prompt_result_ = rc;
+ listener_state_ = ListenerState::Terminating;
+ lock.unlock();
+ if (do_callback) {
+ auto error = resultCB->result(prompt_result_, msg, token);
+ if (!error.isOk()) {
+ ConfUiLog(ERROR) << "Result callback failed " << error.description();
+ }
+ ConfUiLog(INFO) << "Result callback returned.";
+ } else {
+ listener_state_condv_.notify_all();
+ }
+}
+
+// Methods from ::android::hardware::confirmationui::V1_0::IConfirmationUI
+// follow.
+Return<ResponseCode> TrustyConfirmationUI::promptUserConfirmation(
+ const sp<IConfirmationResultCallback>& resultCB, const hidl_string& promptText,
+ const hidl_vec<uint8_t>& extraData, const hidl_string& locale,
+ const hidl_vec<UIOption>& uiOptions) {
+ std::unique_lock<std::mutex> stateLock(listener_state_lock_, std::defer_lock);
+ ConfUiLog(INFO) << "promptUserConfirmation is called";
+
+ if (!stateLock.try_lock()) {
+ return ResponseCode::OperationPending;
+ }
+ switch (listener_state_) {
+ case ListenerState::None:
+ break;
+ case ListenerState::Starting:
+ case ListenerState::SetupDone:
+ case ListenerState::Interactive:
+ return ResponseCode::OperationPending;
+ case ListenerState::Terminating:
+ callback_thread_.join();
+ listener_state_ = ListenerState::None;
+ break;
+ default:
+ return ResponseCode::Unexpected;
+ }
+ assert(listener_state_ == ListenerState::None);
+ listener_state_ = ListenerState::Starting;
+ ConfUiLog(INFO) << "Per promptUserConfirmation, "
+ << "an active TEE UI session starts";
+ current_session_id_++;
+ auto worker = [this](const sp<IConfirmationResultCallback>& resultCB,
+ const hidl_string& promptText, const hidl_vec<uint8_t>& extraData,
+ const hidl_string& locale, const hidl_vec<UIOption>& uiOptions) {
+ RunSession(resultCB, promptText, extraData, locale, uiOptions);
+ };
+ callback_thread_ = std::thread(worker, resultCB, promptText, extraData, locale, uiOptions);
+
+ listener_state_condv_.wait(stateLock, [this] {
+ return listener_state_ == ListenerState::SetupDone ||
+ listener_state_ == ListenerState::Interactive ||
+ listener_state_ == ListenerState::Terminating;
+ });
+ if (listener_state_ == ListenerState::Terminating) {
+ callback_thread_.join();
+ listener_state_ = ListenerState::None;
+ if (prompt_result_ == ResponseCode::Canceled) {
+ // VTS expects this
+ return ResponseCode::OK;
+ }
+ return prompt_result_;
+ }
+ return ResponseCode::OK;
+}
+
+Return<ResponseCode>
+TrustyConfirmationUI::deliverSecureInputEvent(const HardwareAuthToken& auth_token) {
+ ConfUiLog(INFO) << "deliverSecureInputEvent is called";
+ ResponseCode rc = ResponseCode::Ignored;
+ {
+ std::unique_lock<std::mutex> lock(current_session_lock_);
+ if (!current_session_) {
+ return rc;
+ }
+ return current_session_->DeliverSecureInputEvent(auth_token);
+ }
+}
+
+Return<void> TrustyConfirmationUI::abort() {
+ {
+ std::unique_lock<std::mutex> lock(current_session_lock_);
+ if (!current_session_) {
+ return Void();
+ }
+ return current_session_->Abort();
+ }
+}
+
+android::sp<IConfirmationUI> createTrustyConfirmationUI() {
+ return new TrustyConfirmationUI();
+}
+
+} // namespace implementation
+} // namespace V1_0
+} // namespace confirmationui
+} // namespace hardware
+} // namespace android
diff --git a/guest/hals/confirmationui/TrustyConfirmationUI.h b/guest/hals/confirmationui/TrustyConfirmationUI.h
new file mode 100644
index 0000000..1742d88
--- /dev/null
+++ b/guest/hals/confirmationui/TrustyConfirmationUI.h
@@ -0,0 +1,129 @@
+/*
+ * Copyright 2020, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef ANDROID_HARDWARE_CONFIRMATIONUI_V1_0_TRUSTY_CONFIRMATIONUI_H
+#define ANDROID_HARDWARE_CONFIRMATIONUI_V1_0_TRUSTY_CONFIRMATIONUI_H
+
+#include <atomic>
+#include <condition_variable>
+#include <cstdint>
+#include <memory>
+#include <mutex>
+#include <thread>
+
+#include <android/hardware/confirmationui/1.0/IConfirmationUI.h>
+#include <android/hardware/keymaster/4.0/types.h>
+#include <hidl/Status.h>
+#include <teeui/generic_messages.h>
+
+#include "common/libs/concurrency/thread_safe_queue.h"
+#include "common/libs/confui/confui.h"
+#include "common/libs/fs/shared_fd.h"
+#include "guest_session.h"
+
+namespace android {
+namespace hardware {
+namespace confirmationui {
+namespace V1_0 {
+namespace implementation {
+
+using ::android::sp;
+using ::android::hardware::hidl_array;
+using ::android::hardware::hidl_string;
+using ::android::hardware::hidl_vec;
+using ::android::hardware::Return;
+using ::android::hardware::Void;
+
+class TrustyConfirmationUI : public IConfirmationUI {
+ public:
+ using ConfUiMessage = cuttlefish::confui::ConfUiMessage;
+ using ConfUiAckMessage = cuttlefish::confui::ConfUiAckMessage;
+ using ListenerState = GuestSession::ListenerState;
+
+ TrustyConfirmationUI();
+ virtual ~TrustyConfirmationUI();
+ // Methods from ::android::hardware::confirmationui::V1_0::IConfirmationUI
+ // follow.
+ Return<ResponseCode> promptUserConfirmation(const sp<IConfirmationResultCallback>& resultCB,
+ const hidl_string& promptText,
+ const hidl_vec<uint8_t>& extraData,
+ const hidl_string& locale,
+ const hidl_vec<UIOption>& uiOptions) override;
+ Return<ResponseCode> deliverSecureInputEvent(
+ const ::android::hardware::keymaster::V4_0::HardwareAuthToken& secureInputToken) override;
+
+ Return<void> abort() override;
+
+ private:
+ /*
+ * Note for implementation
+ *
+ * The TEE UI session cannot be pre-emptied normally. The session will have an
+ * exclusive control for the input and the screen. Only when something goes
+ * wrong, it can be aborted by abort().
+ *
+ * Another thing is that promptUserConfirmation() may return without waiting
+ * for the resultCB is completed. When it returns early, it still returns
+ * ResponseCode::OK. In that case, the promptUserConfirmation() could actually
+ * fail -- e.g. the input device is broken down afterwards, the user never
+ * gave an input until timeout, etc. Then, the resultCB would be called with
+ * an appropriate error code. However, even in that case, most of the time
+ * promptUserConfirmation() returns OK. Only when the initial set up for
+ * confirmation UI fails, promptUserConfirmation() may return non-OK.
+ *
+ * So, the implementation is roughly:
+ * 1. If there's another session going on, return with ResponseCode::Ignored
+ * and the return is immediate
+ * 2. If there's a zombie, collect the zombie and go to 3
+ * 3. If there's nothing, start a new session in a new thread, and return
+ * the promptUserConfirmation() call as early as possible
+ *
+ * Another issue is to maintain/define the ownership of vsock. For now,
+ * a message fetcher (from the host) will see if the vsock is ok, and
+ * reconnect if not. But, eventually, the new session should establish a
+ * new connection/client vsock, and the new session should own the fetcher
+ * thread.
+ */
+ std::thread callback_thread_;
+ ListenerState listener_state_;
+
+ std::mutex listener_state_lock_;
+ std::condition_variable listener_state_condv_;
+ ResponseCode prompt_result_;
+
+ // client socket to the host
+ int host_vsock_port_;
+ cuttlefish::SharedFD host_fd_;
+
+ // ack, response, command from the host, and the abort command from the guest
+ std::atomic<std::uint32_t> current_session_id_;
+ std::mutex current_session_lock_;
+ std::unique_ptr<GuestSession> current_session_;
+ std::thread host_cmd_fetcher_thread_;
+
+ cuttlefish::SharedFD ConnectToHost();
+ void HostMessageFetcherLoop();
+ void RunSession(sp<IConfirmationResultCallback> resultCB, hidl_string promptText,
+ hidl_vec<uint8_t> extraData, hidl_string locale, hidl_vec<UIOption> uiOptions);
+};
+
+} // namespace implementation
+} // namespace V1_0
+} // namespace confirmationui
+} // namespace hardware
+} // namespace android
+
+#endif // ANDROID_HARDWARE_CONFIRMATIONUI_V1_0_TRUSTY_CONFIRMATIONUI_H
diff --git a/guest/hals/confirmationui/[email protected] b/guest/hals/confirmationui/[email protected]
new file mode 100644
index 0000000..81dfd49
--- /dev/null
+++ b/guest/hals/confirmationui/[email protected]
@@ -0,0 +1,5 @@
+service confirmationui-1-0 /vendor/bin/hw/[email protected]
+ interface [email protected]::IConfirmationUI default
+ class hal
+ user system
+ group drmrpc input system
diff --git a/guest/hals/ril/reference-libril/[email protected] b/guest/hals/confirmationui/[email protected]
similarity index 62%
rename from guest/hals/ril/reference-libril/[email protected]
rename to guest/hals/confirmationui/[email protected]
index 97bf66d..9008b87 100644
--- a/guest/hals/ril/reference-libril/[email protected]
+++ b/guest/hals/confirmationui/[email protected]
@@ -1,10 +1,10 @@
<manifest version="1.0" type="device">
<hal format="hidl">
- <name>android.hardware.radio.config</name>
+ <name>android.hardware.confirmationui</name>
<transport>hwbinder</transport>
- <version>1.3</version>
+ <version>1.0</version>
<interface>
- <name>IRadioConfig</name>
+ <name>IConfirmationUI</name>
<instance>default</instance>
</interface>
</hal>
diff --git a/guest/hals/confirmationui/guest_session.cpp b/guest/hals/confirmationui/guest_session.cpp
new file mode 100644
index 0000000..aa2ab12
--- /dev/null
+++ b/guest/hals/confirmationui/guest_session.cpp
@@ -0,0 +1,265 @@
+/*
+ *
+ * Copyright 2021, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "guest_session.h"
+
+#include <future>
+
+namespace android {
+namespace hardware {
+namespace confirmationui {
+namespace V1_0 {
+namespace implementation {
+using TeeuiRc = teeui::ResponseCode;
+
+GuestSession::ResultTriple GuestSession::PromptUserConfirmation() {
+ std::unique_lock<std::mutex> stateLock(listener_state_lock_);
+ /*
+ * This is the main listener thread function. The listener thread life cycle
+ * is equivalent to the life cycle of a single confirmation request. The life
+ * cycle is divided in four phases.
+ * * The starting phase:
+ * * Drives the cuttlefish confirmation UI session on the host side, too
+ *
+ * Note: During the starting phase the hwbinder service thread is blocked and
+ * waiting for possible Errors. If the setup phase concludes successfully, the
+ * hwbinder service thread gets unblocked and returns successfully. Errors
+ * that occur after the first phase are delivered by callback interface.
+ *
+ * For cuttlefish, it means that the guest will conduct a blocking wait for
+ * an ack to kStart.
+ *
+ * * The 2nd phase - non interactive phase
+ * * After a grace period:
+ * * guest will pick up cuttlefish host's ack to kStart
+ *
+ * * The 3rd phase - interactive phase
+ * * We wait to any external event
+ * * Abort
+ * * Secure user input asserted
+ * * The result is fetched from the TA.
+ *
+ * * The 4th phase - cleanup
+ * * Sending the kStop command to the cuttlefish host, and wait for ack
+ */
+
+ GuestSession::ResultTriple error;
+ auto& error_rc = std::get<ResponseCode>(error);
+ error_rc = ResponseCode::SystemError;
+
+ CHECK(listener_state_ == ListenerState::Starting) << "ListenerState should be Starting";
+
+ // initiate prompt
+ ConfUiLog(INFO) << "Initiating prompt";
+ const std::uint32_t payload_lower_bound =
+ static_cast<std::uint32_t>(prompt_text_.size() + extra_data_.size());
+ const std::uint32_t upper_bound =
+ static_cast<std::uint32_t>(cuttlefish::confui::kMaxMessageLength);
+ if (payload_lower_bound > upper_bound) {
+ ConfUiLog(INFO) << "UI message too long to send to the host";
+ // message is too long anyway, and don't send it to the host
+ error_rc = ResponseCode::UIErrorMessageTooLong;
+ return error;
+ }
+ SerializedSend(cuttlefish::confui::SendStartCmd, host_fd_, session_name_, prompt_text_,
+ extra_data_, locale_, ui_options_);
+ ConfUiLog(INFO) << "Session " << GetSessionId() << " started on both the guest and the host";
+
+ auto clean_up_and_get_first = [&]() -> std::unique_ptr<ConfUiMessage> {
+ // blocking wait to get the first msg that belongs to this session
+ while (true) {
+ auto first_curr_session_msg = incoming_msg_queue_.Pop();
+ if (!first_curr_session_msg ||
+ first_curr_session_msg->GetSessionId() != GetSessionId()) {
+ continue;
+ }
+ return std::move(first_curr_session_msg);
+ }
+ };
+
+ /*
+ * Unconditionally wait ack, or host abort
+ *
+ * First couple of messages could be from the previous session.
+ * We should clear them up.
+ *
+ * Even though the guest HAL sends kAbort to the host, the kAbort
+ * does not happen immediately. Between the incoming_msg_queue_.FlushAll()
+ * and the actual abort on the host, there could still be messages
+ * sent from the host to the guest. As these lines are the first read
+ * for the current session, we clear up the preceding messages
+ * from the previous session until we see the message for the current
+ * session.
+ *
+ * Note that abort() call puts the Abort command in the queue. So,
+ * it will also show up in incoming_msg_queue_
+ *
+ */
+ auto first_msg = std::move(clean_up_and_get_first());
+
+ cuttlefish::confui::ConfUiAckMessage& start_ack_msg =
+ static_cast<cuttlefish::confui::ConfUiAckMessage&>(*first_msg);
+ if (!start_ack_msg.IsSuccess()) {
+ // handle errors: MALFORMED_UTF8 or Message too long
+ const std::string error_msg = start_ack_msg.GetStatusMessage();
+ if (error_msg == cuttlefish::confui::HostError::kMessageTooLongError) {
+ ConfUiLog(ERROR) << "Message + Extra data + Meta info were too long";
+ error_rc = ResponseCode::UIErrorMessageTooLong;
+ }
+ if (error_msg == cuttlefish::confui::HostError::kIncorrectUTF8) {
+ ConfUiLog(ERROR) << "Message is incorrectly UTF-encoded";
+ error_rc = ResponseCode::UIErrorMalformedUTF8Encoding;
+ }
+ return error;
+ }
+
+ // ############################## Start 2nd Phase #############################################
+ listener_state_ = ListenerState::SetupDone;
+ ConfUiLog(INFO) << "Transition to SetupDone";
+ stateLock.unlock();
+ listener_state_condv_.notify_all();
+
+ // cuttlefish does not need the second phase to implement HAL APIs
+ // input was already prepared before the confirmation UI screen was rendered
+
+ // ############################## Start 3rd Phase - interactive phase #########################
+ stateLock.lock();
+ listener_state_ = ListenerState::Interactive;
+ ConfUiLog(INFO) << "Transition to Interactive";
+ stateLock.unlock();
+ listener_state_condv_.notify_all();
+
+ // give deliverSecureInputEvent a chance to interrupt
+
+ // wait for an input but should not block deliverSecureInputEvent or Abort
+ // Thus, it should not hold the stateLock
+ std::mutex input_ready_mtx;
+ std::condition_variable input_ready_cv_;
+ std::unique_lock<std::mutex> input_ready_lock(input_ready_mtx);
+ bool input_ready = false;
+ auto wait_input_and_signal = [&]() -> std::unique_ptr<ConfUiMessage> {
+ auto msg = incoming_msg_queue_.Pop();
+ {
+ std::unique_lock<std::mutex> lock(input_ready_mtx);
+ input_ready = true;
+ input_ready_cv_.notify_one();
+ }
+ return msg;
+ };
+ auto input_and_signal_future = std::async(std::launch::async, wait_input_and_signal);
+ input_ready_cv_.wait(input_ready_lock, [&]() { return input_ready; });
+ // now an input is ready, so let's acquire the stateLock
+
+ stateLock.lock();
+ auto user_or_abort = input_and_signal_future.get();
+
+ if (user_or_abort->GetType() == cuttlefish::confui::ConfUiCmd::kAbort) {
+ ConfUiLog(ERROR) << "Abort called or the user/host aborted"
+ << " while waiting user response";
+ return {ResponseCode::Aborted, {}, {}};
+ }
+ if (user_or_abort->GetType() == cuttlefish::confui::ConfUiCmd::kCliAck) {
+ auto& ack_msg = static_cast<cuttlefish::confui::ConfUiAckMessage&>(*user_or_abort);
+ if (ack_msg.IsSuccess()) {
+ ConfUiLog(ERROR) << "When host failed, it is supposed to send "
+ << "kCliAck with fail, but this is kCliAck with success";
+ }
+ error_rc = ResponseCode::SystemError;
+ return error;
+ }
+ cuttlefish::confui::ConfUiCliResponseMessage& user_response =
+ static_cast<cuttlefish::confui::ConfUiCliResponseMessage&>(*user_or_abort);
+
+ // pick, see if it is response, abort cmd
+ // handle abort or error response here
+ ConfUiLog(INFO) << "Making up the result";
+
+ // make up the result triple
+ if (user_response.GetResponse() == cuttlefish::confui::UserResponse::kCancel) {
+ SerializedSend(cuttlefish::confui::SendStopCmd, host_fd_, GetSessionId());
+ return {ResponseCode::Canceled, {}, {}};
+ }
+
+ if (user_response.GetResponse() != cuttlefish::confui::UserResponse::kConfirm) {
+ ConfUiLog(ERROR) << "Unexpected user response that is " << user_response.GetResponse();
+ return error;
+ }
+ SerializedSend(cuttlefish::confui::SendStopCmd, host_fd_, GetSessionId());
+ // ############################## Start 4th Phase - cleanup ##################################
+ return {ResponseCode::OK, user_response.GetMessage(), user_response.GetSign()};
+}
+
+Return<ResponseCode> GuestSession::DeliverSecureInputEvent(
+ const android::hardware::keymaster::V4_0::HardwareAuthToken& auth_token) {
+ ResponseCode rc = ResponseCode::Ignored;
+ {
+ /*
+ * deliverSecureInputEvent is only used by the VTS test to mock human input. A correct
+ * implementation responds with a mock confirmation token signed with a test key. The
+ * problem is that the non interactive grace period was not formalized in the HAL spec,
+ * so that the VTS test does not account for the grace period. (It probably should.)
+ * This means we can only pass the VTS test if we block until the grace period is over
+ * (SetupDone -> Interactive) before we deliver the input event.
+ *
+ * The true secure input is delivered by a different mechanism and gets ignored -
+ * not queued - until the grace period is over.
+ *
+ */
+ std::unique_lock<std::mutex> stateLock(listener_state_lock_);
+ listener_state_condv_.wait(stateLock,
+ [this] { return listener_state_ != ListenerState::SetupDone; });
+ if (listener_state_ != ListenerState::Interactive) return ResponseCode::Ignored;
+ if (static_cast<TestModeCommands>(auth_token.challenge) == TestModeCommands::OK_EVENT) {
+ SerializedSend(cuttlefish::confui::SendUserSelection, host_fd_, GetSessionId(),
+ cuttlefish::confui::UserResponse::kConfirm);
+ } else {
+ SerializedSend(cuttlefish::confui::SendUserSelection, host_fd_, GetSessionId(),
+ cuttlefish::confui::UserResponse::kCancel);
+ }
+ rc = ResponseCode::OK;
+ }
+ listener_state_condv_.notify_all();
+ // VTS test expect an OK response if the event was successfully delivered.
+ // But since the TA returns the callback response now, we have to translate
+ // Canceled into OK. Canceled is only returned if the delivered event canceled
+ // the operation, which means that the event was successfully delivered. Thus
+ // we return OK.
+ if (rc == ResponseCode::Canceled) return ResponseCode::OK;
+ return rc;
+}
+
+Return<void> GuestSession::Abort() {
+ {
+ std::unique_lock<std::mutex> stateLock(listener_state_lock_);
+ if (listener_state_ == ListenerState::SetupDone ||
+ listener_state_ == ListenerState::Interactive) {
+ if (host_fd_->IsOpen()) {
+ SerializedSend(cuttlefish::confui::SendAbortCmd, host_fd_, GetSessionId());
+ }
+ using cuttlefish::confui::ConfUiAbortMessage;
+ auto local_abort_cmd = std::make_unique<ConfUiAbortMessage>(GetSessionId());
+ incoming_msg_queue_.Push(std::move(local_abort_cmd));
+ }
+ }
+ listener_state_condv_.notify_all();
+ return Void();
+}
+} // namespace implementation
+} // namespace V1_0
+} // namespace confirmationui
+} // namespace hardware
+} // namespace android
diff --git a/guest/hals/confirmationui/guest_session.h b/guest/hals/confirmationui/guest_session.h
new file mode 100644
index 0000000..0dceffe
--- /dev/null
+++ b/guest/hals/confirmationui/guest_session.h
@@ -0,0 +1,146 @@
+/*
+ *
+ * Copyright 2021, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <android-base/logging.h>
+#include <android/hardware/confirmationui/1.0/types.h>
+#include <android/hardware/keymaster/4.0/types.h>
+
+#include <condition_variable>
+#include <cstdint>
+#include <memory>
+#include <mutex>
+#include <string>
+#include <tuple>
+#include <vector>
+
+#include "common/libs/concurrency/thread_safe_queue.h"
+#include "common/libs/confui/confui.h"
+#include "common/libs/fs/shared_fd.h"
+
+namespace android {
+namespace hardware {
+namespace confirmationui {
+namespace V1_0 {
+namespace implementation {
+class GuestSession {
+ public:
+ using ConfUiMessage = cuttlefish::confui::ConfUiMessage;
+ using ConfUiAckMessage = cuttlefish::confui::ConfUiAckMessage;
+ using Queue = cuttlefish::ThreadSafeQueue<std::unique_ptr<ConfUiMessage>>;
+ using QueueImpl = Queue::QueueImpl;
+
+ enum class ListenerState : uint32_t {
+ None = 0,
+ Starting = 1,
+ SetupDone = 2,
+ Interactive = 3,
+ Terminating = 4,
+ };
+
+ GuestSession(const std::uint32_t session_id, ListenerState& listener_state,
+ std::mutex& listener_state_lock, std::condition_variable& listener_state_condv,
+ cuttlefish::SharedFD host_fd, const teeui::MsgString& promptText,
+ const teeui::MsgVector<uint8_t>& extraData, const teeui::MsgString& locale,
+ const teeui::MsgVector<teeui::UIOption>& uiOptions)
+ : prompt_text_{promptText.begin(), promptText.end()}, extra_data_{extraData.begin(),
+ extraData.end()},
+ locale_{locale.begin(), locale.end()}, ui_options_{uiOptions.begin(), uiOptions.end()},
+ listener_state_(listener_state), listener_state_lock_(listener_state_lock),
+ listener_state_condv_(listener_state_condv), host_fd_{host_fd},
+ session_name_(MakeName(session_id)),
+ incoming_msg_queue_(
+ 20, [this](GuestSession::QueueImpl* impl) { return QueueFullHandler(impl); }) {}
+
+ ~GuestSession() {
+ // the thread for PromptUserConfirmation is still alive
+ // the host_fd_ may be alive
+ auto state = listener_state_;
+ if (state == ListenerState::SetupDone || state == ListenerState::Interactive) {
+ Abort();
+ }
+ // TODO(kwstephenkim): close fd once Session takes the ownership of fd
+ // join host_cmd_fetcher_thread_ once Session takes the ownership of fd
+ }
+
+ using ResultTriple =
+ std::tuple<ResponseCode, teeui::MsgVector<uint8_t>, teeui::MsgVector<uint8_t>>;
+ ResultTriple PromptUserConfirmation();
+
+ Return<ResponseCode> DeliverSecureInputEvent(
+ const ::android::hardware::keymaster::V4_0::HardwareAuthToken& secureInputToken);
+
+ Return<void> Abort();
+ std::string GetSessionId() const { return session_name_; }
+
+ void Push(std::unique_ptr<ConfUiMessage>&& msg) { incoming_msg_queue_.Push(std::move(msg)); }
+
+ private:
+ template <typename F, typename... Args>
+ bool SerializedSend(F&& f, cuttlefish::SharedFD fd, Args&&... args) {
+ if (!fd->IsOpen()) {
+ return false;
+ }
+ std::unique_lock<std::mutex> lock(send_serializer_mtx_);
+ return f(fd, std::forward<Args>(args)...);
+ }
+
+ void QueueFullHandler(QueueImpl* queue_impl) {
+ if (!queue_impl) {
+ LOG(ERROR) << "Registered queue handler is "
+ << "seeing nullptr for queue implementation.";
+ return;
+ }
+ const auto n = (queue_impl->size()) / 2;
+ // pop front half
+ queue_impl->erase(queue_impl->begin(), queue_impl->begin() + n);
+ }
+
+ std::string MakeName(const std::uint32_t i) const {
+ return "ConfirmationUiSession" + std::to_string(i);
+ }
+ std::string prompt_text_;
+ std::vector<std::uint8_t> extra_data_;
+ std::string locale_;
+ std::vector<teeui::UIOption> ui_options_;
+
+ /*
+ * lister_state_lock_ coordinates multiple threads that may
+ * call the three Confirmation UI HAL APIs concurrently
+ */
+ ListenerState& listener_state_;
+ std::mutex& listener_state_lock_;
+ std::condition_variable& listener_state_condv_;
+ cuttlefish::SharedFD host_fd_;
+
+ const std::string session_name_;
+ Queue incoming_msg_queue_;
+
+ /*
+ * multiple threads could try to write on the vsock at the
+ * same time. E.g. promptUserConfirmation() thread sends
+ * a command while abort() is being called. The abort() thread
+ * will try to write an abort command concurrently.
+ */
+ std::mutex send_serializer_mtx_;
+};
+} // namespace implementation
+} // namespace V1_0
+} // namespace confirmationui
+} // namespace hardware
+} // namespace android
diff --git a/guest/hals/confirmationui/include/TrustyConfirmationuiHal.h b/guest/hals/confirmationui/include/TrustyConfirmationuiHal.h
new file mode 100644
index 0000000..2ab9389
--- /dev/null
+++ b/guest/hals/confirmationui/include/TrustyConfirmationuiHal.h
@@ -0,0 +1,33 @@
+/*
+ * Copyright 2020, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <android/hardware/confirmationui/1.0/IConfirmationUI.h>
+
+namespace android {
+namespace hardware {
+namespace confirmationui {
+namespace V1_0 {
+namespace implementation {
+
+android::sp<IConfirmationUI> createTrustyConfirmationUI();
+
+} // namespace implementation
+} // namespace V1_0
+} // namespace confirmationui
+} // namespace hardware
+} // namespace android
diff --git a/guest/hals/confirmationui/include/TrustyIpc.h b/guest/hals/confirmationui/include/TrustyIpc.h
new file mode 100644
index 0000000..eb764bc
--- /dev/null
+++ b/guest/hals/confirmationui/include/TrustyIpc.h
@@ -0,0 +1,80 @@
+/*
+ * Copyright 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <stdint.h>
+
+/*
+ * This interface is shared between Android and Trusty. There is a copy in each
+ * repository. They must be kept in sync.
+ */
+
+#define CONFIRMATIONUI_PORT "com.android.trusty.confirmationui"
+
+/**
+ * enum confirmationui_cmd - command identifiers for ConfirmationUI interface
+ * @CONFIRMATIONUI_RESP_BIT: response bit set as part of response
+ * @CONFIRMATIONUI_REQ_SHIFT: number of bits used by response bit
+ * @CONFIRMATIONUI_CMD_INIT: command to initialize session
+ * @CONFIRMATIONUI_CMD_MSG: command to send ConfirmationUI messages
+ */
+enum confirmationui_cmd : uint32_t {
+ CONFIRMATIONUI_RESP_BIT = 1,
+ CONFIRMATIONUI_REQ_SHIFT = 1,
+
+ CONFIRMATIONUI_CMD_INIT = (1 << CONFIRMATIONUI_REQ_SHIFT),
+ CONFIRMATIONUI_CMD_MSG = (2 << CONFIRMATIONUI_REQ_SHIFT),
+};
+
+/**
+ * struct confirmationui_hdr - header for ConfirmationUI messages
+ * @cmd: command identifier
+ *
+ * Note that no messages return a status code. Any error on the server side
+ * results in the connection being closed. So, operations can be assumed to be
+ * successful if they return a response.
+ */
+struct confirmationui_hdr {
+ uint32_t cmd;
+};
+
+/**
+ * struct confirmationui_init_req - arguments for request to initialize a
+ * session
+ * @shm_len: length of memory region being shared
+ *
+ * A handle to a memory region must be sent along with this message. This memory
+ * is send to ConfirmationUI messages.
+ */
+struct confirmationui_init_req {
+ uint32_t shm_len;
+};
+
+/**
+ * struct confirmationui_msg_args - arguments for sending a message
+ * @msg_len: length of message being sent
+ *
+ * Contents of the message are located in the shared memory region that is
+ * established using %CONFIRMATIONUI_CMD_INIT.
+ *
+ * ConfirmationUI messages can travel both ways.
+ */
+struct confirmationui_msg_args {
+ uint32_t msg_len;
+};
+
+#define CONFIRMATIONUI_MAX_MSG_SIZE 0x2000
diff --git a/guest/hals/confirmationui/service.cpp b/guest/hals/confirmationui/service.cpp
new file mode 100644
index 0000000..dd7e84b
--- /dev/null
+++ b/guest/hals/confirmationui/service.cpp
@@ -0,0 +1,35 @@
+/*
+ * Copyright 2020, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <android-base/logging.h>
+#include <hidl/HidlTransportSupport.h>
+
+#include <TrustyConfirmationuiHal.h>
+
+using android::sp;
+using android::hardware::confirmationui::V1_0::implementation::createTrustyConfirmationUI;
+
+int main() {
+ ::android::hardware::configureRpcThreadpool(1, true /*willJoinThreadpool*/);
+ auto service = createTrustyConfirmationUI();
+ auto status = service->registerAsService();
+ if (status != android::OK) {
+ LOG(FATAL) << "Could not register service for ConfirmationUI 1.0 (" << status << ")";
+ return -1;
+ }
+ ::android::hardware::joinRpcThreadpool();
+ return -1;
+}
diff --git a/guest/hals/health/Android.bp b/guest/hals/health/Android.bp
index 81d19a3..cb9d866 100644
--- a/guest/hals/health/Android.bp
+++ b/guest/hals/health/Android.bp
@@ -17,6 +17,58 @@
default_applicable_licenses: ["Android-Apache-2.0"],
}
+cc_defaults {
+ name: "android.hardware.health-service.cuttlefish-defaults",
+ relative_install_path: "hw",
+ vintf_fragments: ["android.hardware.health-service.cuttlefish.xml"],
+
+ srcs: [
+ "health-aidl.cpp",
+ ],
+
+ cflags: [
+ "-Wall",
+ "-Werror",
+ ],
+
+ static_libs: [
+ "android.hardware.health-translate-ndk",
+ "libbatterymonitor",
+ "libhealthloop",
+ "libhealth_aidl_impl",
+ ],
+
+ shared_libs: [
+ "libbase",
+ "libbinder_ndk",
+ "libcutils",
+ "libhidlbase",
+ "liblog",
+ "libutils",
+ "android.hardware.health-V1-ndk",
+ ],
+
+ defaults: ["enabled_on_p_and_later"],
+}
+
+cc_binary {
+ name: "android.hardware.health-service.cuttlefish",
+ defaults: ["android.hardware.health-service.cuttlefish-defaults"],
+ proprietary: true,
+ init_rc: ["android.hardware.health-service.cuttlefish.rc"],
+ overrides: ["charger"],
+}
+
+cc_binary {
+ name: "android.hardware.health-service.cuttlefish_recovery",
+ defaults: ["android.hardware.health-service.cuttlefish-defaults"],
+ recovery: true,
+ init_rc: ["android.hardware.health-service.cuttlefish_recovery.rc"],
+ overrides: ["charger.recovery"],
+}
+
+// Deprecated. Retained to be used on other devices. It is not installed on cuttlefish.
+// TODO(b/210183170): Delete once other devices transition to the AIDL HAL.
cc_library_shared {
name: "[email protected]",
stem: "[email protected]",
@@ -26,7 +78,7 @@
relative_install_path: "hw",
srcs: [
- "health.cpp",
+ "health-hidl.cpp",
],
cflags: [
diff --git a/guest/hals/health/android.hardware.health-service.cuttlefish.rc b/guest/hals/health/android.hardware.health-service.cuttlefish.rc
new file mode 100644
index 0000000..8c2f153
--- /dev/null
+++ b/guest/hals/health/android.hardware.health-service.cuttlefish.rc
@@ -0,0 +1,8 @@
+service vendor.health-cuttlefish /vendor/bin/hw/android.hardware.health-service.cuttlefish
+ class hal
+ user system
+ group system
+ capabilities WAKE_ALARM BLOCK_SUSPEND
+ file /dev/kmsg w
+
+# cuttlefish has no charger mode.
diff --git a/guest/hals/health/android.hardware.health-service.cuttlefish.xml b/guest/hals/health/android.hardware.health-service.cuttlefish.xml
new file mode 100644
index 0000000..98026cb
--- /dev/null
+++ b/guest/hals/health/android.hardware.health-service.cuttlefish.xml
@@ -0,0 +1,7 @@
+<manifest version="1.0" type="device">
+ <hal format="aidl">
+ <name>android.hardware.health</name>
+ <version>1</version>
+ <fqname>IHealth/default</fqname>
+ </hal>
+</manifest>
diff --git a/guest/hals/health/android.hardware.health-service.cuttlefish_recovery.rc b/guest/hals/health/android.hardware.health-service.cuttlefish_recovery.rc
new file mode 100644
index 0000000..58e4405
--- /dev/null
+++ b/guest/hals/health/android.hardware.health-service.cuttlefish_recovery.rc
@@ -0,0 +1,7 @@
+service vendor.health-cuttlefish /system/bin/hw/android.hardware.health-service.cuttlefish_recovery
+ class hal
+ seclabel u:r:hal_health_default:s0
+ user system
+ group system
+ capabilities WAKE_ALARM BLOCK_SUSPEND
+ file /dev/kmsg w
diff --git a/guest/hals/health/health-aidl.cpp b/guest/hals/health/health-aidl.cpp
new file mode 100644
index 0000000..595971f
--- /dev/null
+++ b/guest/hals/health/health-aidl.cpp
@@ -0,0 +1,119 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#define LOG_TAG "android.hardware.health-service.cuttlefish"
+
+#include <memory>
+#include <string_view>
+
+#include <android-base/logging.h>
+#include <android/binder_interface_utils.h>
+#include <health-impl/Health.h>
+#include <health/utils.h>
+
+using ::aidl::android::hardware::health::BatteryHealth;
+using ::aidl::android::hardware::health::BatteryStatus;
+using ::aidl::android::hardware::health::HalHealthLoop;
+using ::aidl::android::hardware::health::Health;
+using ::aidl::android::hardware::health::HealthInfo;
+using ::aidl::android::hardware::health::IHealth;
+using ::android::hardware::health::InitHealthdConfig;
+using ::ndk::ScopedAStatus;
+using ::ndk::SharedRefBase;
+using namespace std::literals;
+
+namespace aidl::android::hardware::health {
+
+// Health HAL implementation for cuttlefish. Note that in this implementation,
+// cuttlefish pretends to be a device with a battery being charged.
+// Implementations on real devices should not insert these fake values. For
+// example, a battery-less device should report batteryPresent = false and
+// batteryStatus = UNKNOWN.
+
+class HealthImpl : public Health {
+ public:
+ // Inherit constructor.
+ using Health::Health;
+ virtual ~HealthImpl() {}
+
+ ScopedAStatus getChargeCounterUah(int32_t* out) override;
+ ScopedAStatus getCurrentNowMicroamps(int32_t* out) override;
+ ScopedAStatus getCurrentAverageMicroamps(int32_t* out) override;
+ ScopedAStatus getCapacity(int32_t* out) override;
+ ScopedAStatus getChargeStatus(BatteryStatus* out) override;
+
+ protected:
+ void UpdateHealthInfo(HealthInfo* health_info) override;
+};
+
+void HealthImpl::UpdateHealthInfo(HealthInfo* health_info) {
+ health_info->chargerAcOnline = true;
+ health_info->chargerUsbOnline = true;
+ health_info->chargerWirelessOnline = false;
+ health_info->maxChargingCurrentMicroamps = 500000;
+ health_info->maxChargingVoltageMicrovolts = 5000000;
+ health_info->batteryStatus = BatteryStatus::CHARGING;
+ health_info->batteryHealth = BatteryHealth::GOOD;
+ health_info->batteryPresent = true;
+ health_info->batteryLevel = 85;
+ health_info->batteryVoltageMillivolts = 3600;
+ health_info->batteryTemperatureTenthsCelsius = 350;
+ health_info->batteryCurrentMicroamps = 400000;
+ health_info->batteryCycleCount = 32;
+ health_info->batteryFullChargeUah = 4000000;
+ health_info->batteryChargeCounterUah = 1900000;
+ health_info->batteryTechnology = "Li-ion";
+}
+
+ScopedAStatus HealthImpl::getChargeCounterUah(int32_t* out) {
+ *out = 1900000;
+ return ScopedAStatus::ok();
+}
+
+ScopedAStatus HealthImpl::getCurrentNowMicroamps(int32_t* out) {
+ *out = 400000;
+ return ScopedAStatus::ok();
+}
+
+ScopedAStatus HealthImpl::getCurrentAverageMicroamps(int32_t*) {
+ return ScopedAStatus::fromExceptionCode(EX_UNSUPPORTED_OPERATION);
+}
+
+ScopedAStatus HealthImpl::getCapacity(int32_t* out) {
+ *out = 85;
+ return ScopedAStatus::ok();
+}
+
+ScopedAStatus HealthImpl::getChargeStatus(BatteryStatus* out) {
+ *out = BatteryStatus::CHARGING;
+ return ScopedAStatus::ok();
+}
+
+} // namespace aidl::android::hardware::health
+
+int main(int, [[maybe_unused]] char** argv) {
+#ifdef __ANDROID_RECOVERY__
+ android::base::InitLogging(argv, android::base::KernelLogger);
+#endif
+ // Cuttlefish does not support offline-charging mode, hence do not handle
+ // --charger option.
+ using aidl::android::hardware::health::HealthImpl;
+ LOG(INFO) << "Starting health HAL.";
+ auto config = std::make_unique<healthd_config>();
+ InitHealthdConfig(config.get());
+ auto binder = SharedRefBase::make<HealthImpl>("default", std::move(config));
+ auto hal_health_loop = std::make_shared<HalHealthLoop>(binder, binder);
+ return hal_health_loop->StartLoop();
+}
diff --git a/guest/hals/health/health.cpp b/guest/hals/health/health-hidl.cpp
similarity index 100%
rename from guest/hals/health/health.cpp
rename to guest/hals/health/health-hidl.cpp
diff --git a/guest/hals/health/storage/Android.bp b/guest/hals/health/storage/Android.bp
index dc57d0f..1ca807d1 100644
--- a/guest/hals/health/storage/Android.bp
+++ b/guest/hals/health/storage/Android.bp
@@ -38,7 +38,7 @@
],
shared_libs: [
- "android.hardware.health.storage-V1-ndk_platform",
+ "android.hardware.health.storage-V1-ndk",
"libbase",
"libbinder_ndk",
"libutils",
diff --git a/host/commands/tapsetiff/Android.bp b/guest/hals/hostapd/Android.bp
similarity index 67%
copy from host/commands/tapsetiff/Android.bp
copy to guest/hals/hostapd/Android.bp
index 1d7dedb..a72b3b7 100644
--- a/host/commands/tapsetiff/Android.bp
+++ b/guest/hals/hostapd/Android.bp
@@ -1,5 +1,4 @@
-//
-// Copyright (C) 2020 The Android Open Source Project
+// Copyright (C) 2021 The Android Open Source Project
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
@@ -14,10 +13,15 @@
// limitations under the License.
package {
- default_applicable_licenses: ["Android-Apache-2.0"],
+ default_applicable_licenses: [
+ "external_wpa_supplicant_8_license",
+ ],
}
-sh_binary_host {
- name: "tapsetiff",
- src: "tapsetiff.py",
+cc_binary {
+ name: "hostapd_cf",
+ defaults: ["hostapd_defaults"],
+ static_libs: [
+ "lib_driver_cmd_simulated_cf_bp",
+ ],
}
diff --git a/guest/hals/identity/Android.bp b/guest/hals/identity/Android.bp
new file mode 100644
index 0000000..c0142ee
--- /dev/null
+++ b/guest/hals/identity/Android.bp
@@ -0,0 +1,61 @@
+package {
+ default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+cc_binary {
+ name: "android.hardware.identity-service.remote",
+ relative_install_path: "hw",
+ init_rc: ["android.hardware.identity-service.remote.rc"],
+ vintf_fragments: ["android.hardware.identity-service.remote.xml"],
+ vendor: true,
+ cflags: [
+ "-Wall",
+ "-Wextra",
+ "-g",
+ ],
+ shared_libs: [
+ "liblog",
+ "libcrypto",
+ "libbinder_ndk",
+ "libkeymaster_messages",
+ ],
+ static_libs: [
+ "libbase",
+ "libcppbor_external",
+ "libcppcose_rkp",
+ "libutils",
+ "libsoft_attestation_cert",
+ "libkeymaster_portable",
+ "libsoft_attestation_cert",
+ "libpuresoftkeymasterdevice",
+ "android.hardware.identity-support-lib",
+ "android.hardware.identity-V3-ndk",
+ "android.hardware.keymaster-V3-ndk",
+ "android.hardware.security.keymint-V1-ndk",
+ ],
+ local_include_dirs: [
+ "common",
+ "libeic",
+ ],
+ srcs: [
+ "service.cpp",
+ "RemoteSecureHardwareProxy.cpp",
+ "common/IdentityCredential.cpp",
+ "common/IdentityCredentialStore.cpp",
+ "common/WritableIdentityCredential.cpp",
+ "libeic/EicCbor.c",
+ "libeic/EicPresentation.c",
+ "libeic/EicProvisioning.c",
+ "libeic/EicOpsImpl.cc",
+ ],
+ required: [
+ "android.hardware.identity_credential.remote.xml",
+ ],
+}
+
+prebuilt_etc {
+ name: "android.hardware.identity_credential.remote.xml",
+ sub_dir: "permissions",
+ vendor: true,
+ src: "android.hardware.identity_credential.remote.xml",
+}
diff --git a/guest/hals/identity/OWNERS b/guest/hals/identity/OWNERS
new file mode 100644
index 0000000..190f95c
--- /dev/null
+++ b/guest/hals/identity/OWNERS
@@ -0,0 +1 @@
+include /platform/hardware/interfaces:/identity/OWNERS
diff --git a/guest/hals/identity/RemoteSecureHardwareProxy.cpp b/guest/hals/identity/RemoteSecureHardwareProxy.cpp
new file mode 100644
index 0000000..3ec8aaa
--- /dev/null
+++ b/guest/hals/identity/RemoteSecureHardwareProxy.cpp
@@ -0,0 +1,412 @@
+/*
+ * Copyright 2021, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#define LOG_TAG "RemoteSecureHardwareProxy"
+
+#include "RemoteSecureHardwareProxy.h"
+
+#include <android/hardware/identity/support/IdentityCredentialSupport.h>
+
+#include <android-base/logging.h>
+#include <android-base/stringprintf.h>
+#include <string.h>
+
+#include <openssl/sha.h>
+
+#include <openssl/aes.h>
+#include <openssl/bn.h>
+#include <openssl/crypto.h>
+#include <openssl/ec.h>
+#include <openssl/err.h>
+#include <openssl/evp.h>
+#include <openssl/hkdf.h>
+#include <openssl/hmac.h>
+#include <openssl/objects.h>
+#include <openssl/pem.h>
+#include <openssl/pkcs12.h>
+#include <openssl/rand.h>
+#include <openssl/x509.h>
+#include <openssl/x509_vfy.h>
+
+#include <libeic.h>
+
+using ::std::optional;
+using ::std::string;
+using ::std::tuple;
+using ::std::vector;
+
+namespace android::hardware::identity {
+
+// ----------------------------------------------------------------------
+
+RemoteSecureHardwareProvisioningProxy::RemoteSecureHardwareProvisioningProxy() {
+}
+
+RemoteSecureHardwareProvisioningProxy::
+ ~RemoteSecureHardwareProvisioningProxy() {}
+
+bool RemoteSecureHardwareProvisioningProxy::shutdown() {
+ LOG(INFO) << "RemoteSecureHardwarePresentationProxy shutdown";
+ return true;
+}
+
+bool RemoteSecureHardwareProvisioningProxy::initialize(bool testCredential) {
+ LOG(INFO) << "RemoteSecureHardwareProvisioningProxy created, "
+ "sizeof(EicProvisioning): "
+ << sizeof(EicProvisioning);
+ return eicProvisioningInit(&ctx_, testCredential);
+}
+
+bool RemoteSecureHardwareProvisioningProxy::initializeForUpdate(
+ bool testCredential, string docType,
+ vector<uint8_t> encryptedCredentialKeys) {
+ return eicProvisioningInitForUpdate(
+ &ctx_, testCredential, docType.c_str(), docType.size(),
+ encryptedCredentialKeys.data(), encryptedCredentialKeys.size());
+}
+
+// Returns public key certificate.
+optional<vector<uint8_t>>
+RemoteSecureHardwareProvisioningProxy::createCredentialKey(
+ const vector<uint8_t>& challenge, const vector<uint8_t>& applicationId) {
+ uint8_t publicKeyCert[4096];
+ size_t publicKeyCertSize = sizeof publicKeyCert;
+ if (!eicProvisioningCreateCredentialKey(
+ &ctx_, challenge.data(), challenge.size(), applicationId.data(),
+ applicationId.size(), publicKeyCert, &publicKeyCertSize)) {
+ return {};
+ }
+ vector<uint8_t> pubKeyCert(publicKeyCertSize);
+ memcpy(pubKeyCert.data(), publicKeyCert, publicKeyCertSize);
+ return pubKeyCert;
+}
+
+bool RemoteSecureHardwareProvisioningProxy::startPersonalization(
+ int accessControlProfileCount, vector<int> entryCounts,
+ const string& docType, size_t expectedProofOfProvisioningSize) {
+ if (!eicProvisioningStartPersonalization(
+ &ctx_, accessControlProfileCount, entryCounts.data(),
+ entryCounts.size(), docType.c_str(), docType.size(),
+ expectedProofOfProvisioningSize)) {
+ return false;
+ }
+ return true;
+}
+
+// Returns MAC (28 bytes).
+optional<vector<uint8_t>>
+RemoteSecureHardwareProvisioningProxy::addAccessControlProfile(
+ int id, const vector<uint8_t>& readerCertificate,
+ bool userAuthenticationRequired, uint64_t timeoutMillis,
+ uint64_t secureUserId) {
+ vector<uint8_t> mac(28);
+ uint8_t scratchSpace[512];
+ if (!eicProvisioningAddAccessControlProfile(
+ &ctx_, id, readerCertificate.data(), readerCertificate.size(),
+ userAuthenticationRequired, timeoutMillis, secureUserId, mac.data(),
+ scratchSpace, sizeof(scratchSpace))) {
+ return {};
+ }
+ return mac;
+}
+
+bool RemoteSecureHardwareProvisioningProxy::beginAddEntry(
+ const vector<int>& accessControlProfileIds, const string& nameSpace,
+ const string& name, uint64_t entrySize) {
+ uint8_t scratchSpace[512];
+ vector<uint8_t> uint8AccessControlProfileIds;
+ for (size_t i = 0; i < accessControlProfileIds.size(); i++) {
+ uint8AccessControlProfileIds.push_back(accessControlProfileIds[i] & 0xFF);
+ }
+
+ return eicProvisioningBeginAddEntry(
+ &ctx_, uint8AccessControlProfileIds.data(),
+ uint8AccessControlProfileIds.size(), nameSpace.c_str(), nameSpace.size(),
+ name.c_str(), name.size(), entrySize, scratchSpace, sizeof(scratchSpace));
+}
+
+// Returns encryptedContent.
+optional<vector<uint8_t>> RemoteSecureHardwareProvisioningProxy::addEntryValue(
+ const vector<int>& accessControlProfileIds, const string& nameSpace,
+ const string& name, const vector<uint8_t>& content) {
+ vector<uint8_t> eicEncryptedContent;
+ uint8_t scratchSpace[512];
+ vector<uint8_t> uint8AccessControlProfileIds;
+ for (size_t i = 0; i < accessControlProfileIds.size(); i++) {
+ uint8AccessControlProfileIds.push_back(accessControlProfileIds[i] & 0xFF);
+ }
+
+ eicEncryptedContent.resize(content.size() + 28);
+ if (!eicProvisioningAddEntryValue(&ctx_, uint8AccessControlProfileIds.data(),
+ uint8AccessControlProfileIds.size(),
+ nameSpace.c_str(), nameSpace.size(),
+ name.c_str(), name.size(), content.data(),
+ content.size(), eicEncryptedContent.data(),
+ scratchSpace, sizeof(scratchSpace))) {
+ return {};
+ }
+ return eicEncryptedContent;
+}
+
+// Returns signatureOfToBeSigned (EIC_ECDSA_P256_SIGNATURE_SIZE bytes).
+optional<vector<uint8_t>>
+RemoteSecureHardwareProvisioningProxy::finishAddingEntries() {
+ vector<uint8_t> signatureOfToBeSigned(EIC_ECDSA_P256_SIGNATURE_SIZE);
+ if (!eicProvisioningFinishAddingEntries(&ctx_,
+ signatureOfToBeSigned.data())) {
+ return {};
+ }
+ return signatureOfToBeSigned;
+}
+
+// Returns encryptedCredentialKeys.
+optional<vector<uint8_t>>
+RemoteSecureHardwareProvisioningProxy::finishGetCredentialData(
+ const string& docType) {
+ vector<uint8_t> encryptedCredentialKeys(116);
+ size_t size = encryptedCredentialKeys.size();
+ if (!eicProvisioningFinishGetCredentialData(
+ &ctx_, docType.c_str(), docType.size(),
+ encryptedCredentialKeys.data(), &size)) {
+ return {};
+ }
+ encryptedCredentialKeys.resize(size);
+ return encryptedCredentialKeys;
+}
+
+// ----------------------------------------------------------------------
+
+RemoteSecureHardwarePresentationProxy::RemoteSecureHardwarePresentationProxy() {
+}
+
+RemoteSecureHardwarePresentationProxy::
+ ~RemoteSecureHardwarePresentationProxy() {}
+
+bool RemoteSecureHardwarePresentationProxy::initialize(
+ bool testCredential, string docType,
+ vector<uint8_t> encryptedCredentialKeys) {
+ LOG(INFO) << "RemoteSecureHardwarePresentationProxy created, "
+ "sizeof(EicPresentation): "
+ << sizeof(EicPresentation);
+ return eicPresentationInit(&ctx_, testCredential, docType.c_str(),
+ docType.size(), encryptedCredentialKeys.data(),
+ encryptedCredentialKeys.size());
+}
+
+// Returns publicKeyCert (1st component) and signingKeyBlob (2nd component)
+optional<pair<vector<uint8_t>, vector<uint8_t>>>
+RemoteSecureHardwarePresentationProxy::generateSigningKeyPair(string docType,
+ time_t now) {
+ uint8_t publicKeyCert[512];
+ size_t publicKeyCertSize = sizeof(publicKeyCert);
+ vector<uint8_t> signingKeyBlob(60);
+
+ if (!eicPresentationGenerateSigningKeyPair(
+ &ctx_, docType.c_str(), docType.size(), now, publicKeyCert,
+ &publicKeyCertSize, signingKeyBlob.data())) {
+ return {};
+ }
+
+ vector<uint8_t> cert;
+ cert.resize(publicKeyCertSize);
+ memcpy(cert.data(), publicKeyCert, publicKeyCertSize);
+
+ return std::make_pair(cert, signingKeyBlob);
+}
+
+// Returns private key
+optional<vector<uint8_t>>
+RemoteSecureHardwarePresentationProxy::createEphemeralKeyPair() {
+ vector<uint8_t> priv(EIC_P256_PRIV_KEY_SIZE);
+ if (!eicPresentationCreateEphemeralKeyPair(&ctx_, priv.data())) {
+ return {};
+ }
+ return priv;
+}
+
+optional<uint64_t>
+RemoteSecureHardwarePresentationProxy::createAuthChallenge() {
+ uint64_t challenge;
+ if (!eicPresentationCreateAuthChallenge(&ctx_, &challenge)) {
+ return {};
+ }
+ return challenge;
+}
+
+bool RemoteSecureHardwarePresentationProxy::shutdown() {
+ LOG(INFO) << "RemoteSecureHardwarePresentationProxy shutdown";
+ return true;
+}
+
+bool RemoteSecureHardwarePresentationProxy::pushReaderCert(
+ const vector<uint8_t>& certX509) {
+ return eicPresentationPushReaderCert(&ctx_, certX509.data(), certX509.size());
+}
+
+bool RemoteSecureHardwarePresentationProxy::validateRequestMessage(
+ const vector<uint8_t>& sessionTranscript,
+ const vector<uint8_t>& requestMessage, int coseSignAlg,
+ const vector<uint8_t>& readerSignatureOfToBeSigned) {
+ return eicPresentationValidateRequestMessage(
+ &ctx_, sessionTranscript.data(), sessionTranscript.size(),
+ requestMessage.data(), requestMessage.size(), coseSignAlg,
+ readerSignatureOfToBeSigned.data(), readerSignatureOfToBeSigned.size());
+}
+
+bool RemoteSecureHardwarePresentationProxy::setAuthToken(
+ uint64_t challenge, uint64_t secureUserId, uint64_t authenticatorId,
+ int hardwareAuthenticatorType, uint64_t timeStamp,
+ const vector<uint8_t>& mac, uint64_t verificationTokenChallenge,
+ uint64_t verificationTokenTimestamp, int verificationTokenSecurityLevel,
+ const vector<uint8_t>& verificationTokenMac) {
+ return eicPresentationSetAuthToken(
+ &ctx_, challenge, secureUserId, authenticatorId,
+ hardwareAuthenticatorType, timeStamp, mac.data(), mac.size(),
+ verificationTokenChallenge, verificationTokenTimestamp,
+ verificationTokenSecurityLevel, verificationTokenMac.data(),
+ verificationTokenMac.size());
+}
+
+optional<bool>
+RemoteSecureHardwarePresentationProxy::validateAccessControlProfile(
+ int id, const vector<uint8_t>& readerCertificate,
+ bool userAuthenticationRequired, int timeoutMillis, uint64_t secureUserId,
+ const vector<uint8_t>& mac) {
+ bool accessGranted = false;
+ uint8_t scratchSpace[512];
+ if (!eicPresentationValidateAccessControlProfile(
+ &ctx_, id, readerCertificate.data(), readerCertificate.size(),
+ userAuthenticationRequired, timeoutMillis, secureUserId, mac.data(),
+ &accessGranted, scratchSpace, sizeof(scratchSpace))) {
+ return {};
+ }
+ return accessGranted;
+}
+
+bool RemoteSecureHardwarePresentationProxy::startRetrieveEntries() {
+ return eicPresentationStartRetrieveEntries(&ctx_);
+}
+
+bool RemoteSecureHardwarePresentationProxy::calcMacKey(
+ const vector<uint8_t>& sessionTranscript,
+ const vector<uint8_t>& readerEphemeralPublicKey,
+ const vector<uint8_t>& signingKeyBlob, const string& docType,
+ unsigned int numNamespacesWithValues,
+ size_t expectedProofOfProvisioningSize) {
+ if (signingKeyBlob.size() != 60) {
+ eicDebug("Unexpected size %zd of signingKeyBlob, expected 60",
+ signingKeyBlob.size());
+ return false;
+ }
+ return eicPresentationCalcMacKey(
+ &ctx_, sessionTranscript.data(), sessionTranscript.size(),
+ readerEphemeralPublicKey.data(), signingKeyBlob.data(), docType.c_str(),
+ docType.size(), numNamespacesWithValues, expectedProofOfProvisioningSize);
+}
+
+AccessCheckResult
+RemoteSecureHardwarePresentationProxy::startRetrieveEntryValue(
+ const string& nameSpace, const string& name,
+ unsigned int newNamespaceNumEntries, int32_t entrySize,
+ const vector<int32_t>& accessControlProfileIds) {
+ uint8_t scratchSpace[512];
+ vector<uint8_t> uint8AccessControlProfileIds;
+ for (size_t i = 0; i < accessControlProfileIds.size(); i++) {
+ uint8AccessControlProfileIds.push_back(accessControlProfileIds[i] & 0xFF);
+ }
+
+ EicAccessCheckResult result = eicPresentationStartRetrieveEntryValue(
+ &ctx_, nameSpace.c_str(), nameSpace.size(), name.c_str(), name.size(),
+ newNamespaceNumEntries, entrySize, uint8AccessControlProfileIds.data(),
+ uint8AccessControlProfileIds.size(), scratchSpace, sizeof(scratchSpace));
+ switch (result) {
+ case EIC_ACCESS_CHECK_RESULT_OK:
+ return AccessCheckResult::kOk;
+ case EIC_ACCESS_CHECK_RESULT_NO_ACCESS_CONTROL_PROFILES:
+ return AccessCheckResult::kNoAccessControlProfiles;
+ case EIC_ACCESS_CHECK_RESULT_FAILED:
+ return AccessCheckResult::kFailed;
+ case EIC_ACCESS_CHECK_RESULT_USER_AUTHENTICATION_FAILED:
+ return AccessCheckResult::kUserAuthenticationFailed;
+ case EIC_ACCESS_CHECK_RESULT_READER_AUTHENTICATION_FAILED:
+ return AccessCheckResult::kReaderAuthenticationFailed;
+ }
+ eicDebug("Unknown result with code %d, returning kFailed", (int)result);
+ return AccessCheckResult::kFailed;
+}
+
+optional<vector<uint8_t>>
+RemoteSecureHardwarePresentationProxy::retrieveEntryValue(
+ const vector<uint8_t>& encryptedContent, const string& nameSpace,
+ const string& name, const vector<int32_t>& accessControlProfileIds) {
+ uint8_t scratchSpace[512];
+ vector<uint8_t> uint8AccessControlProfileIds;
+ for (size_t i = 0; i < accessControlProfileIds.size(); i++) {
+ uint8AccessControlProfileIds.push_back(accessControlProfileIds[i] & 0xFF);
+ }
+
+ vector<uint8_t> content;
+ content.resize(encryptedContent.size() - 28);
+ if (!eicPresentationRetrieveEntryValue(
+ &ctx_, encryptedContent.data(), encryptedContent.size(),
+ content.data(), nameSpace.c_str(), nameSpace.size(), name.c_str(),
+ name.size(), uint8AccessControlProfileIds.data(),
+ uint8AccessControlProfileIds.size(), scratchSpace,
+ sizeof(scratchSpace))) {
+ return {};
+ }
+ return content;
+}
+
+optional<vector<uint8_t>>
+RemoteSecureHardwarePresentationProxy::finishRetrieval() {
+ vector<uint8_t> mac(32);
+ size_t macSize = 32;
+ if (!eicPresentationFinishRetrieval(&ctx_, mac.data(), &macSize)) {
+ return {};
+ }
+ mac.resize(macSize);
+ return mac;
+}
+
+optional<vector<uint8_t>>
+RemoteSecureHardwarePresentationProxy::deleteCredential(
+ const string& docType, const vector<uint8_t>& challenge,
+ bool includeChallenge, size_t proofOfDeletionCborSize) {
+ vector<uint8_t> signatureOfToBeSigned(EIC_ECDSA_P256_SIGNATURE_SIZE);
+ if (!eicPresentationDeleteCredential(
+ &ctx_, docType.c_str(), docType.size(), challenge.data(),
+ challenge.size(), includeChallenge, proofOfDeletionCborSize,
+ signatureOfToBeSigned.data())) {
+ return {};
+ }
+ return signatureOfToBeSigned;
+}
+
+optional<vector<uint8_t>> RemoteSecureHardwarePresentationProxy::proveOwnership(
+ const string& docType, bool testCredential,
+ const vector<uint8_t>& challenge, size_t proofOfOwnershipCborSize) {
+ vector<uint8_t> signatureOfToBeSigned(EIC_ECDSA_P256_SIGNATURE_SIZE);
+ if (!eicPresentationProveOwnership(&ctx_, docType.c_str(), docType.size(),
+ testCredential, challenge.data(),
+ challenge.size(), proofOfOwnershipCborSize,
+ signatureOfToBeSigned.data())) {
+ return {};
+ }
+ return signatureOfToBeSigned;
+}
+
+} // namespace android::hardware::identity
diff --git a/guest/hals/identity/RemoteSecureHardwareProxy.h b/guest/hals/identity/RemoteSecureHardwareProxy.h
new file mode 100644
index 0000000..39cb422
--- /dev/null
+++ b/guest/hals/identity/RemoteSecureHardwareProxy.h
@@ -0,0 +1,169 @@
+/*
+ * Copyright 2021, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef ANDROID_HARDWARE_IDENTITY_FAKESECUREHARDWAREPROXY_H
+#define ANDROID_HARDWARE_IDENTITY_FAKESECUREHARDWAREPROXY_H
+
+#include <libeic.h>
+
+#include "SecureHardwareProxy.h"
+
+namespace android::hardware::identity {
+
+// This implementation uses libEmbeddedIC in-process.
+//
+class RemoteSecureHardwareProvisioningProxy
+ : public SecureHardwareProvisioningProxy {
+ public:
+ RemoteSecureHardwareProvisioningProxy();
+ virtual ~RemoteSecureHardwareProvisioningProxy();
+
+ bool initialize(bool testCredential) override;
+
+ bool initializeForUpdate(bool testCredential, string docType,
+ vector<uint8_t> encryptedCredentialKeys) override;
+
+ bool shutdown() override;
+
+ // Returns public key certificate.
+ optional<vector<uint8_t>> createCredentialKey(
+ const vector<uint8_t>& challenge,
+ const vector<uint8_t>& applicationId) override;
+
+ bool startPersonalization(int accessControlProfileCount,
+ vector<int> entryCounts, const string& docType,
+ size_t expectedProofOfProvisioningSize) override;
+
+ // Returns MAC (28 bytes).
+ optional<vector<uint8_t>> addAccessControlProfile(
+ int id, const vector<uint8_t>& readerCertificate,
+ bool userAuthenticationRequired, uint64_t timeoutMillis,
+ uint64_t secureUserId) override;
+
+ bool beginAddEntry(const vector<int>& accessControlProfileIds,
+ const string& nameSpace, const string& name,
+ uint64_t entrySize) override;
+
+ // Returns encryptedContent.
+ optional<vector<uint8_t>> addEntryValue(
+ const vector<int>& accessControlProfileIds, const string& nameSpace,
+ const string& name, const vector<uint8_t>& content) override;
+
+ // Returns signatureOfToBeSigned (EIC_ECDSA_P256_SIGNATURE_SIZE bytes).
+ optional<vector<uint8_t>> finishAddingEntries() override;
+
+ // Returns encryptedCredentialKeys (80 bytes).
+ optional<vector<uint8_t>> finishGetCredentialData(
+ const string& docType) override;
+
+ protected:
+ EicProvisioning ctx_;
+};
+
+// This implementation uses libEmbeddedIC in-process.
+//
+class RemoteSecureHardwarePresentationProxy
+ : public SecureHardwarePresentationProxy {
+ public:
+ RemoteSecureHardwarePresentationProxy();
+ virtual ~RemoteSecureHardwarePresentationProxy();
+
+ bool initialize(bool testCredential, string docType,
+ vector<uint8_t> encryptedCredentialKeys) override;
+
+ // Returns publicKeyCert (1st component) and signingKeyBlob (2nd component)
+ optional<pair<vector<uint8_t>, vector<uint8_t>>> generateSigningKeyPair(
+ string docType, time_t now) override;
+
+ // Returns private key
+ optional<vector<uint8_t>> createEphemeralKeyPair() override;
+
+ optional<uint64_t> createAuthChallenge() override;
+
+ bool startRetrieveEntries() override;
+
+ bool setAuthToken(uint64_t challenge, uint64_t secureUserId,
+ uint64_t authenticatorId, int hardwareAuthenticatorType,
+ uint64_t timeStamp, const vector<uint8_t>& mac,
+ uint64_t verificationTokenChallenge,
+ uint64_t verificationTokenTimestamp,
+ int verificationTokenSecurityLevel,
+ const vector<uint8_t>& verificationTokenMac) override;
+
+ bool pushReaderCert(const vector<uint8_t>& certX509) override;
+
+ optional<bool> validateAccessControlProfile(
+ int id, const vector<uint8_t>& readerCertificate,
+ bool userAuthenticationRequired, int timeoutMillis, uint64_t secureUserId,
+ const vector<uint8_t>& mac) override;
+
+ bool validateRequestMessage(
+ const vector<uint8_t>& sessionTranscript,
+ const vector<uint8_t>& requestMessage, int coseSignAlg,
+ const vector<uint8_t>& readerSignatureOfToBeSigned) override;
+
+ bool calcMacKey(const vector<uint8_t>& sessionTranscript,
+ const vector<uint8_t>& readerEphemeralPublicKey,
+ const vector<uint8_t>& signingKeyBlob, const string& docType,
+ unsigned int numNamespacesWithValues,
+ size_t expectedProofOfProvisioningSize) override;
+
+ AccessCheckResult startRetrieveEntryValue(
+ const string& nameSpace, const string& name,
+ unsigned int newNamespaceNumEntries, int32_t entrySize,
+ const vector<int32_t>& accessControlProfileIds) override;
+
+ optional<vector<uint8_t>> retrieveEntryValue(
+ const vector<uint8_t>& encryptedContent, const string& nameSpace,
+ const string& name,
+ const vector<int32_t>& accessControlProfileIds) override;
+
+ optional<vector<uint8_t>> finishRetrieval() override;
+
+ optional<vector<uint8_t>> deleteCredential(
+ const string& docType, const vector<uint8_t>& challenge,
+ bool includeChallenge, size_t proofOfDeletionCborSize) override;
+
+ optional<vector<uint8_t>> proveOwnership(
+ const string& docType, bool testCredential,
+ const vector<uint8_t>& challenge,
+ size_t proofOfOwnershipCborSize) override;
+
+ bool shutdown() override;
+
+ protected:
+ EicPresentation ctx_;
+};
+
+// Factory implementation.
+//
+class RemoteSecureHardwareProxyFactory : public SecureHardwareProxyFactory {
+ public:
+ RemoteSecureHardwareProxyFactory() {}
+ virtual ~RemoteSecureHardwareProxyFactory() {}
+
+ sp<SecureHardwareProvisioningProxy> createProvisioningProxy() override {
+ return new RemoteSecureHardwareProvisioningProxy();
+ }
+
+ sp<SecureHardwarePresentationProxy> createPresentationProxy() override {
+ return new RemoteSecureHardwarePresentationProxy();
+ }
+};
+
+} // namespace android::hardware::identity
+
+#endif // ANDROID_HARDWARE_IDENTITY_FAKESECUREHARDWAREPROXY_H
diff --git a/guest/hals/identity/android.hardware.identity-service.remote.rc b/guest/hals/identity/android.hardware.identity-service.remote.rc
new file mode 100644
index 0000000..e1dc7a9
--- /dev/null
+++ b/guest/hals/identity/android.hardware.identity-service.remote.rc
@@ -0,0 +1,3 @@
+service vendor.identity-remote /vendor/bin/hw/android.hardware.identity-service.remote
+ class hal
+ user nobody
diff --git a/guest/hals/identity/android.hardware.identity-service.remote.xml b/guest/hals/identity/android.hardware.identity-service.remote.xml
new file mode 100644
index 0000000..a074250
--- /dev/null
+++ b/guest/hals/identity/android.hardware.identity-service.remote.xml
@@ -0,0 +1,10 @@
+<manifest version="1.0" type="device">
+ <hal format="aidl">
+ <name>android.hardware.identity</name>
+ <version>3</version>
+ <interface>
+ <name>IIdentityCredentialStore</name>
+ <instance>default</instance>
+ </interface>
+ </hal>
+</manifest>
diff --git a/guest/hals/identity/android.hardware.identity_credential.remote.xml b/guest/hals/identity/android.hardware.identity_credential.remote.xml
new file mode 100644
index 0000000..5149792
--- /dev/null
+++ b/guest/hals/identity/android.hardware.identity_credential.remote.xml
@@ -0,0 +1,18 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright 2021 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<permissions>
+ <feature name="android.hardware.identity_credential" version="202101" />
+</permissions>
diff --git a/guest/hals/identity/common/IdentityCredential.cpp b/guest/hals/identity/common/IdentityCredential.cpp
new file mode 100644
index 0000000..3555c53
--- /dev/null
+++ b/guest/hals/identity/common/IdentityCredential.cpp
@@ -0,0 +1,945 @@
+/*
+ * Copyright 2019, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#define LOG_TAG "IdentityCredential"
+
+#include "IdentityCredential.h"
+#include "IdentityCredentialStore.h"
+
+#include <android/hardware/identity/support/IdentityCredentialSupport.h>
+
+#include <string.h>
+
+#include <android-base/logging.h>
+#include <android-base/stringprintf.h>
+
+#include <cppbor.h>
+#include <cppbor_parse.h>
+
+#include "SecureHardwareProxy.h"
+#include "WritableIdentityCredential.h"
+
+namespace aidl::android::hardware::identity {
+
+using ::aidl::android::hardware::keymaster::Timestamp;
+using ::android::base::StringPrintf;
+using ::std::optional;
+
+using namespace ::android::hardware::identity;
+
+int IdentityCredential::initialize() {
+ if (credentialData_.size() == 0) {
+ LOG(ERROR) << "CredentialData is empty";
+ return IIdentityCredentialStore::STATUS_INVALID_DATA;
+ }
+ auto [item, _, message] = cppbor::parse(credentialData_);
+ if (item == nullptr) {
+ LOG(ERROR) << "CredentialData is not valid CBOR: " << message;
+ return IIdentityCredentialStore::STATUS_INVALID_DATA;
+ }
+
+ const cppbor::Array* arrayItem = item->asArray();
+ if (arrayItem == nullptr || arrayItem->size() != 3) {
+ LOG(ERROR) << "CredentialData is not an array with three elements";
+ return IIdentityCredentialStore::STATUS_INVALID_DATA;
+ }
+
+ const cppbor::Tstr* docTypeItem = (*arrayItem)[0]->asTstr();
+ const cppbor::Bool* testCredentialItem =
+ ((*arrayItem)[1]->asSimple() != nullptr
+ ? ((*arrayItem)[1]->asSimple()->asBool())
+ : nullptr);
+ const cppbor::Bstr* encryptedCredentialKeysItem = (*arrayItem)[2]->asBstr();
+ if (docTypeItem == nullptr || testCredentialItem == nullptr ||
+ encryptedCredentialKeysItem == nullptr) {
+ LOG(ERROR) << "CredentialData unexpected item types";
+ return IIdentityCredentialStore::STATUS_INVALID_DATA;
+ }
+
+ docType_ = docTypeItem->value();
+ testCredential_ = testCredentialItem->value();
+
+ encryptedCredentialKeys_ = encryptedCredentialKeysItem->value();
+ if (!hwProxy_->initialize(testCredential_, docType_,
+ encryptedCredentialKeys_)) {
+ LOG(ERROR) << "hwProxy->initialize failed";
+ return false;
+ }
+
+ return IIdentityCredentialStore::STATUS_OK;
+}
+
+ndk::ScopedAStatus IdentityCredential::deleteCredential(
+ vector<uint8_t>* outProofOfDeletionSignature) {
+ return deleteCredentialCommon({}, false, outProofOfDeletionSignature);
+}
+
+ndk::ScopedAStatus IdentityCredential::deleteCredentialWithChallenge(
+ const vector<uint8_t>& challenge,
+ vector<uint8_t>* outProofOfDeletionSignature) {
+ return deleteCredentialCommon(challenge, true, outProofOfDeletionSignature);
+}
+
+ndk::ScopedAStatus IdentityCredential::deleteCredentialCommon(
+ const vector<uint8_t>& challenge, bool includeChallenge,
+ vector<uint8_t>* outProofOfDeletionSignature) {
+ if (challenge.size() > 32) {
+ return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
+ IIdentityCredentialStore::STATUS_INVALID_DATA, "Challenge too big"));
+ }
+
+ cppbor::Array array = {"ProofOfDeletion", docType_, testCredential_};
+ if (includeChallenge) {
+ array = {"ProofOfDeletion", docType_, challenge, testCredential_};
+ }
+
+ vector<uint8_t> proofOfDeletionCbor = array.encode();
+ vector<uint8_t> podDigest = support::sha256(proofOfDeletionCbor);
+
+ optional<vector<uint8_t>> signatureOfToBeSigned = hwProxy_->deleteCredential(
+ docType_, challenge, includeChallenge, proofOfDeletionCbor.size());
+ if (!signatureOfToBeSigned) {
+ return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
+ IIdentityCredentialStore::STATUS_FAILED,
+ "Error signing ProofOfDeletion"));
+ }
+
+ optional<vector<uint8_t>> signature =
+ support::coseSignEcDsaWithSignature(signatureOfToBeSigned.value(),
+ proofOfDeletionCbor, // data
+ {}); // certificateChain
+ if (!signature) {
+ return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
+ IIdentityCredentialStore::STATUS_FAILED, "Error signing data"));
+ }
+
+ *outProofOfDeletionSignature = signature.value();
+ return ndk::ScopedAStatus::ok();
+}
+
+ndk::ScopedAStatus IdentityCredential::proveOwnership(
+ const vector<uint8_t>& challenge,
+ vector<uint8_t>* outProofOfOwnershipSignature) {
+ if (challenge.size() > 32) {
+ return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
+ IIdentityCredentialStore::STATUS_INVALID_DATA, "Challenge too big"));
+ }
+
+ cppbor::Array array;
+ array = {"ProofOfOwnership", docType_, challenge, testCredential_};
+ vector<uint8_t> proofOfOwnershipCbor = array.encode();
+ vector<uint8_t> podDigest = support::sha256(proofOfOwnershipCbor);
+
+ optional<vector<uint8_t>> signatureOfToBeSigned = hwProxy_->proveOwnership(
+ docType_, testCredential_, challenge, proofOfOwnershipCbor.size());
+ if (!signatureOfToBeSigned) {
+ return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
+ IIdentityCredentialStore::STATUS_FAILED,
+ "Error signing ProofOfOwnership"));
+ }
+
+ optional<vector<uint8_t>> signature =
+ support::coseSignEcDsaWithSignature(signatureOfToBeSigned.value(),
+ proofOfOwnershipCbor, // data
+ {}); // certificateChain
+ if (!signature) {
+ return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
+ IIdentityCredentialStore::STATUS_FAILED, "Error signing data"));
+ }
+
+ *outProofOfOwnershipSignature = signature.value();
+ return ndk::ScopedAStatus::ok();
+}
+
+ndk::ScopedAStatus IdentityCredential::createEphemeralKeyPair(
+ vector<uint8_t>* outKeyPair) {
+ optional<vector<uint8_t>> ephemeralPriv = hwProxy_->createEphemeralKeyPair();
+ if (!ephemeralPriv) {
+ return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
+ IIdentityCredentialStore::STATUS_FAILED,
+ "Error creating ephemeral key"));
+ }
+ optional<vector<uint8_t>> keyPair =
+ support::ecPrivateKeyToKeyPair(ephemeralPriv.value());
+ if (!keyPair) {
+ return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
+ IIdentityCredentialStore::STATUS_FAILED,
+ "Error creating ephemeral key-pair"));
+ }
+
+ // Stash public key of this key-pair for later check in startRetrieval().
+ optional<vector<uint8_t>> publicKey =
+ support::ecKeyPairGetPublicKey(keyPair.value());
+ if (!publicKey) {
+ LOG(ERROR) << "Error getting public part of ephemeral key pair";
+ return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
+ IIdentityCredentialStore::STATUS_FAILED,
+ "Error getting public part of ephemeral key pair"));
+ }
+ ephemeralPublicKey_ = publicKey.value();
+
+ *outKeyPair = keyPair.value();
+ return ndk::ScopedAStatus::ok();
+}
+
+ndk::ScopedAStatus IdentityCredential::setReaderEphemeralPublicKey(
+ const vector<uint8_t>& publicKey) {
+ readerPublicKey_ = publicKey;
+ return ndk::ScopedAStatus::ok();
+}
+
+ndk::ScopedAStatus IdentityCredential::createAuthChallenge(
+ int64_t* outChallenge) {
+ optional<uint64_t> challenge = hwProxy_->createAuthChallenge();
+ if (!challenge) {
+ return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
+ IIdentityCredentialStore::STATUS_FAILED, "Error generating challenge"));
+ }
+ *outChallenge = challenge.value();
+ return ndk::ScopedAStatus::ok();
+}
+
+ndk::ScopedAStatus IdentityCredential::setRequestedNamespaces(
+ const vector<RequestNamespace>& requestNamespaces) {
+ requestNamespaces_ = requestNamespaces;
+ return ndk::ScopedAStatus::ok();
+}
+
+ndk::ScopedAStatus IdentityCredential::setVerificationToken(
+ const VerificationToken& verificationToken) {
+ verificationToken_ = verificationToken;
+ return ndk::ScopedAStatus::ok();
+}
+
+ndk::ScopedAStatus IdentityCredential::startRetrieval(
+ const vector<SecureAccessControlProfile>& accessControlProfiles,
+ const HardwareAuthToken& authToken, const vector<uint8_t>& itemsRequest,
+ const vector<uint8_t>& signingKeyBlob,
+ const vector<uint8_t>& sessionTranscript,
+ const vector<uint8_t>& readerSignature,
+ const vector<int32_t>& requestCounts) {
+ std::unique_ptr<cppbor::Item> sessionTranscriptItem;
+ if (sessionTranscript.size() > 0) {
+ auto [item, _, message] = cppbor::parse(sessionTranscript);
+ if (item == nullptr) {
+ return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
+ IIdentityCredentialStore::STATUS_INVALID_DATA,
+ "SessionTranscript contains invalid CBOR"));
+ }
+ sessionTranscriptItem = std::move(item);
+ }
+ if (numStartRetrievalCalls_ > 0) {
+ if (sessionTranscript_ != sessionTranscript) {
+ LOG(ERROR) << "Session Transcript changed";
+ return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
+ IIdentityCredentialStore::STATUS_SESSION_TRANSCRIPT_MISMATCH,
+ "Passed-in SessionTranscript doesn't match previously used "
+ "SessionTranscript"));
+ }
+ }
+ sessionTranscript_ = sessionTranscript;
+
+ // This resets various state in the TA...
+ if (!hwProxy_->startRetrieveEntries()) {
+ return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
+ IIdentityCredentialStore::STATUS_FAILED,
+ "Error starting retrieving entries"));
+ }
+
+ optional<vector<uint8_t>> signatureOfToBeSigned;
+ if (readerSignature.size() > 0) {
+ signatureOfToBeSigned = support::coseSignGetSignature(readerSignature);
+ if (!signatureOfToBeSigned) {
+ return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
+ IIdentityCredentialStore::STATUS_READER_SIGNATURE_CHECK_FAILED,
+ "Error extracting signatureOfToBeSigned from COSE_Sign1"));
+ }
+ }
+
+ // Feed the auth token to secure hardware only if they're valid.
+ if (authToken.timestamp.milliSeconds != 0) {
+ if (!hwProxy_->setAuthToken(
+ authToken.challenge, authToken.userId, authToken.authenticatorId,
+ int(authToken.authenticatorType), authToken.timestamp.milliSeconds,
+ authToken.mac, verificationToken_.challenge,
+ verificationToken_.timestamp.milliSeconds,
+ int(verificationToken_.securityLevel), verificationToken_.mac)) {
+ return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
+ IIdentityCredentialStore::STATUS_INVALID_DATA, "Invalid Auth Token"));
+ }
+ }
+
+ // We'll be feeding ACPs interleaved with certificates from the reader
+ // certificate chain...
+ vector<SecureAccessControlProfile> remainingAcps = accessControlProfiles;
+
+ // ... and we'll use those ACPs to build up a 32-bit mask indicating which
+ // of the possible 32 ACPs grants access.
+ uint32_t accessControlProfileMask = 0;
+
+ // If there is a signature, validate that it was made with the top-most key in
+ // the certificate chain embedded in the COSE_Sign1 structure.
+ optional<vector<uint8_t>> readerCertificateChain;
+ if (readerSignature.size() > 0) {
+ readerCertificateChain = support::coseSignGetX5Chain(readerSignature);
+ if (!readerCertificateChain) {
+ return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
+ IIdentityCredentialStore::STATUS_READER_SIGNATURE_CHECK_FAILED,
+ "Unable to get reader certificate chain from COSE_Sign1"));
+ }
+
+ // First, feed all the reader certificates to the secure hardware. We start
+ // at the end..
+ optional<vector<vector<uint8_t>>> splitCerts =
+ support::certificateChainSplit(readerCertificateChain.value());
+ if (!splitCerts || splitCerts.value().size() == 0) {
+ return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
+ IIdentityCredentialStore::STATUS_READER_SIGNATURE_CHECK_FAILED,
+ "Error splitting certificate chain from COSE_Sign1"));
+ }
+ for (ssize_t n = splitCerts.value().size() - 1; n >= 0; --n) {
+ const vector<uint8_t>& x509Cert = splitCerts.value()[n];
+ if (!hwProxy_->pushReaderCert(x509Cert)) {
+ return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
+ IIdentityCredentialStore::STATUS_READER_SIGNATURE_CHECK_FAILED,
+ StringPrintf("Error validating reader certificate %zd", n)
+ .c_str()));
+ }
+
+ // If we have ACPs for that particular certificate, send them to the
+ // TA right now...
+ //
+ // Remember in this case certificate equality is done by comparing public
+ // keys, not bitwise comparison of the certificates.
+ //
+ optional<vector<uint8_t>> x509CertPubKey =
+ support::certificateChainGetTopMostKey(x509Cert);
+ if (!x509CertPubKey) {
+ return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
+ IIdentityCredentialStore::STATUS_FAILED,
+ StringPrintf("Error getting public key from reader certificate %zd",
+ n)
+ .c_str()));
+ }
+ vector<SecureAccessControlProfile>::iterator it = remainingAcps.begin();
+ while (it != remainingAcps.end()) {
+ const SecureAccessControlProfile& profile = *it;
+ if (profile.readerCertificate.encodedCertificate.size() == 0) {
+ ++it;
+ continue;
+ }
+ optional<vector<uint8_t>> profilePubKey =
+ support::certificateChainGetTopMostKey(
+ profile.readerCertificate.encodedCertificate);
+ if (!profilePubKey) {
+ return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
+ IIdentityCredentialStore::STATUS_FAILED,
+ "Error getting public key from profile"));
+ }
+ if (profilePubKey.value() == x509CertPubKey.value()) {
+ optional<bool> res = hwProxy_->validateAccessControlProfile(
+ profile.id, profile.readerCertificate.encodedCertificate,
+ profile.userAuthenticationRequired, profile.timeoutMillis,
+ profile.secureUserId, profile.mac);
+ if (!res) {
+ return ndk::ScopedAStatus(
+ AStatus_fromServiceSpecificErrorWithMessage(
+ IIdentityCredentialStore::STATUS_INVALID_DATA,
+ "Error validating access control profile"));
+ }
+ if (res.value()) {
+ accessControlProfileMask |= (1 << profile.id);
+ }
+ it = remainingAcps.erase(it);
+ } else {
+ ++it;
+ }
+ }
+ }
+
+ // ... then pass the request message and have the TA check it's signed by
+ // the key in last certificate we pushed.
+ if (sessionTranscript.size() > 0 && itemsRequest.size() > 0 &&
+ readerSignature.size() > 0) {
+ optional<vector<uint8_t>> tbsSignature =
+ support::coseSignGetSignature(readerSignature);
+ if (!tbsSignature) {
+ return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
+ IIdentityCredentialStore::STATUS_READER_SIGNATURE_CHECK_FAILED,
+ "Error extracting toBeSigned from COSE_Sign1"));
+ }
+ optional<int> coseSignAlg = support::coseSignGetAlg(readerSignature);
+ if (!coseSignAlg) {
+ return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
+ IIdentityCredentialStore::STATUS_READER_SIGNATURE_CHECK_FAILED,
+ "Error extracting signature algorithm from COSE_Sign1"));
+ }
+ if (!hwProxy_->validateRequestMessage(sessionTranscript, itemsRequest,
+ coseSignAlg.value(),
+ tbsSignature.value())) {
+ return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
+ IIdentityCredentialStore::STATUS_READER_SIGNATURE_CHECK_FAILED,
+ "readerMessage is not signed by top-level certificate"));
+ }
+ }
+ }
+
+ // Feed remaining access control profiles...
+ for (const SecureAccessControlProfile& profile : remainingAcps) {
+ optional<bool> res = hwProxy_->validateAccessControlProfile(
+ profile.id, profile.readerCertificate.encodedCertificate,
+ profile.userAuthenticationRequired, profile.timeoutMillis,
+ profile.secureUserId, profile.mac);
+ if (!res) {
+ return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
+ IIdentityCredentialStore::STATUS_INVALID_DATA,
+ "Error validating access control profile"));
+ }
+ if (res.value()) {
+ accessControlProfileMask |= (1 << profile.id);
+ }
+ }
+
+ // TODO: move this check to the TA
+#if 1
+ // To prevent replay-attacks, we check that the public part of the ephemeral
+ // key we previously created, is present in the DeviceEngagement part of
+ // SessionTranscript as a COSE_Key, in uncompressed form.
+ //
+ // We do this by just searching for the X and Y coordinates.
+ if (sessionTranscript.size() > 0) {
+ auto [getXYSuccess, ePubX, ePubY] =
+ support::ecPublicKeyGetXandY(ephemeralPublicKey_);
+ if (!getXYSuccess) {
+ return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
+ IIdentityCredentialStore::STATUS_EPHEMERAL_PUBLIC_KEY_NOT_FOUND,
+ "Error extracting X and Y from ePub"));
+ }
+ if (sessionTranscript.size() > 0 &&
+ !(memmem(sessionTranscript.data(), sessionTranscript.size(),
+ ePubX.data(), ePubX.size()) != nullptr &&
+ memmem(sessionTranscript.data(), sessionTranscript.size(),
+ ePubY.data(), ePubY.size()) != nullptr)) {
+ return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
+ IIdentityCredentialStore::STATUS_EPHEMERAL_PUBLIC_KEY_NOT_FOUND,
+ "Did not find ephemeral public key's X and Y coordinates in "
+ "SessionTranscript (make sure leading zeroes are not used)"));
+ }
+ }
+#endif
+
+ // itemsRequest: If non-empty, contains request data that may be signed by the
+ // reader. The content can be defined in the way appropriate for the
+ // credential, but there are three requirements that must be met to work with
+ // this HAL:
+ if (itemsRequest.size() > 0) {
+ // 1. The content must be a CBOR-encoded structure.
+ auto [item, _, message] = cppbor::parse(itemsRequest);
+ if (item == nullptr) {
+ return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
+ IIdentityCredentialStore::STATUS_INVALID_ITEMS_REQUEST_MESSAGE,
+ "Error decoding CBOR in itemsRequest"));
+ }
+
+ // 2. The CBOR structure must be a map.
+ const cppbor::Map* map = item->asMap();
+ if (map == nullptr) {
+ return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
+ IIdentityCredentialStore::STATUS_INVALID_ITEMS_REQUEST_MESSAGE,
+ "itemsRequest is not a CBOR map"));
+ }
+
+ // 3. The map must contain a key "nameSpaces" whose value contains a map, as
+ // described in
+ // the example below.
+ //
+ // NameSpaces = {
+ // + NameSpace => DataElements ; Requested data elements for each
+ // NameSpace
+ // }
+ //
+ // NameSpace = tstr
+ //
+ // DataElements = {
+ // + DataElement => IntentToRetain
+ // }
+ //
+ // DataElement = tstr
+ // IntentToRetain = bool
+ //
+ // Here's an example of an |itemsRequest| CBOR value satisfying above
+ // requirements 1. through 3.:
+ //
+ // {
+ // 'docType' : 'org.iso.18013-5.2019',
+ // 'nameSpaces' : {
+ // 'org.iso.18013-5.2019' : {
+ // 'Last name' : false,
+ // 'Birth date' : false,
+ // 'First name' : false,
+ // 'Home address' : true
+ // },
+ // 'org.aamva.iso.18013-5.2019' : {
+ // 'Real Id' : false
+ // }
+ // }
+ // }
+ //
+ const cppbor::Map* nsMap = nullptr;
+ for (size_t n = 0; n < map->size(); n++) {
+ const auto& [keyItem, valueItem] = (*map)[n];
+ if (keyItem->type() == cppbor::TSTR &&
+ keyItem->asTstr()->value() == "nameSpaces" &&
+ valueItem->type() == cppbor::MAP) {
+ nsMap = valueItem->asMap();
+ break;
+ }
+ }
+ if (nsMap == nullptr) {
+ return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
+ IIdentityCredentialStore::STATUS_INVALID_ITEMS_REQUEST_MESSAGE,
+ "No nameSpaces map in top-most map"));
+ }
+
+ for (size_t n = 0; n < nsMap->size(); n++) {
+ auto& [nsKeyItem, nsValueItem] = (*nsMap)[n];
+ const cppbor::Tstr* nsKey = nsKeyItem->asTstr();
+ const cppbor::Map* nsInnerMap = nsValueItem->asMap();
+ if (nsKey == nullptr || nsInnerMap == nullptr) {
+ return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
+ IIdentityCredentialStore::STATUS_INVALID_ITEMS_REQUEST_MESSAGE,
+ "Type mismatch in nameSpaces map"));
+ }
+ string requestedNamespace = nsKey->value();
+ set<string> requestedKeys;
+ for (size_t m = 0; m < nsInnerMap->size(); m++) {
+ const auto& [innerMapKeyItem, innerMapValueItem] = (*nsInnerMap)[m];
+ const cppbor::Tstr* nameItem = innerMapKeyItem->asTstr();
+ const cppbor::Simple* simple = innerMapValueItem->asSimple();
+ const cppbor::Bool* intentToRetainItem =
+ (simple != nullptr) ? simple->asBool() : nullptr;
+ if (nameItem == nullptr || intentToRetainItem == nullptr) {
+ return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
+ IIdentityCredentialStore::STATUS_INVALID_ITEMS_REQUEST_MESSAGE,
+ "Type mismatch in value in nameSpaces map"));
+ }
+ requestedKeys.insert(nameItem->value());
+ }
+ requestedNameSpacesAndNames_[requestedNamespace] = requestedKeys;
+ }
+ }
+
+ deviceNameSpacesMap_ = cppbor::Map();
+ currentNameSpaceDeviceNameSpacesMap_ = cppbor::Map();
+
+ requestCountsRemaining_ = requestCounts;
+ currentNameSpace_ = "";
+
+ itemsRequest_ = itemsRequest;
+ signingKeyBlob_ = signingKeyBlob;
+
+ // calculate the size of DeviceNameSpaces. We need to know it ahead of time.
+ calcDeviceNameSpacesSize(accessControlProfileMask);
+
+ // Count the number of non-empty namespaces
+ size_t numNamespacesWithValues = 0;
+ for (size_t n = 0; n < expectedNumEntriesPerNamespace_.size(); n++) {
+ if (expectedNumEntriesPerNamespace_[n] > 0) {
+ numNamespacesWithValues += 1;
+ }
+ }
+
+ // Finally, pass info so the HMAC key can be derived and the TA can start
+ // creating the DeviceNameSpaces CBOR...
+ if (sessionTranscript_.size() > 0 && readerPublicKey_.size() > 0 &&
+ signingKeyBlob.size() > 0) {
+ // We expect the reader ephemeral public key to be same size and curve
+ // as the ephemeral key we generated (e.g. P-256 key), otherwise ECDH
+ // won't work. So its length should be 65 bytes and it should be
+ // starting with 0x04.
+ if (readerPublicKey_.size() != 65 || readerPublicKey_[0] != 0x04) {
+ return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
+ IIdentityCredentialStore::STATUS_FAILED,
+ "Reader public key is not in expected format"));
+ }
+ vector<uint8_t> pubKeyP256(readerPublicKey_.begin() + 1,
+ readerPublicKey_.end());
+ if (!hwProxy_->calcMacKey(sessionTranscript_, pubKeyP256, signingKeyBlob,
+ docType_, numNamespacesWithValues,
+ expectedDeviceNameSpacesSize_)) {
+ return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
+ IIdentityCredentialStore::STATUS_FAILED,
+ "Error starting retrieving entries"));
+ }
+ }
+
+ numStartRetrievalCalls_ += 1;
+ return ndk::ScopedAStatus::ok();
+}
+
+size_t cborNumBytesForLength(size_t length) {
+ if (length < 24) {
+ return 0;
+ } else if (length <= 0xff) {
+ return 1;
+ } else if (length <= 0xffff) {
+ return 2;
+ } else if (length <= 0xffffffff) {
+ return 4;
+ }
+ return 8;
+}
+
+size_t cborNumBytesForTstr(const string& value) {
+ return 1 + cborNumBytesForLength(value.size()) + value.size();
+}
+
+void IdentityCredential::calcDeviceNameSpacesSize(
+ uint32_t accessControlProfileMask) {
+ /*
+ * This is how DeviceNameSpaces is defined:
+ *
+ * DeviceNameSpaces = {
+ * * NameSpace => DeviceSignedItems
+ * }
+ * DeviceSignedItems = {
+ * + DataItemName => DataItemValue
+ * }
+ *
+ * Namespace = tstr
+ * DataItemName = tstr
+ * DataItemValue = any
+ *
+ * This function will calculate its length using knowledge of how CBOR is
+ * encoded.
+ */
+ size_t ret = 0;
+ vector<unsigned int> numEntriesPerNamespace;
+ for (const RequestNamespace& rns : requestNamespaces_) {
+ vector<RequestDataItem> itemsToInclude;
+
+ for (const RequestDataItem& rdi : rns.items) {
+ // If we have a CBOR request message, skip if item isn't in it
+ if (itemsRequest_.size() > 0) {
+ const auto& it = requestedNameSpacesAndNames_.find(rns.namespaceName);
+ if (it == requestedNameSpacesAndNames_.end()) {
+ continue;
+ }
+ const set<string>& dataItemNames = it->second;
+ if (dataItemNames.find(rdi.name) == dataItemNames.end()) {
+ continue;
+ }
+ }
+
+ // Access is granted if at least one of the profiles grants access.
+ //
+ // If an item is configured without any profiles, access is denied.
+ //
+ bool authorized = false;
+ for (auto id : rdi.accessControlProfileIds) {
+ if (accessControlProfileMask & (1 << id)) {
+ authorized = true;
+ break;
+ }
+ }
+ if (!authorized) {
+ continue;
+ }
+
+ itemsToInclude.push_back(rdi);
+ }
+
+ numEntriesPerNamespace.push_back(itemsToInclude.size());
+
+ // If no entries are to be in the namespace, we don't include it in
+ // the CBOR...
+ if (itemsToInclude.size() == 0) {
+ continue;
+ }
+
+ // Key: NameSpace
+ ret += cborNumBytesForTstr(rns.namespaceName);
+
+ // Value: Open the DeviceSignedItems map
+ ret += 1 + cborNumBytesForLength(itemsToInclude.size());
+
+ for (const RequestDataItem& item : itemsToInclude) {
+ // Key: DataItemName
+ ret += cborNumBytesForTstr(item.name);
+
+ // Value: DataItemValue - entryData.size is the length of serialized CBOR
+ // so we use that.
+ ret += item.size;
+ }
+ }
+
+ // Now that we know the number of namespaces with values, we know how many
+ // bytes the DeviceNamespaces map in the beginning is going to take up.
+ ret += 1 + cborNumBytesForLength(numEntriesPerNamespace.size());
+
+ expectedDeviceNameSpacesSize_ = ret;
+ expectedNumEntriesPerNamespace_ = numEntriesPerNamespace;
+}
+
+ndk::ScopedAStatus IdentityCredential::startRetrieveEntryValue(
+ const string& nameSpace, const string& name, int32_t entrySize,
+ const vector<int32_t>& accessControlProfileIds) {
+ if (name.empty()) {
+ return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
+ IIdentityCredentialStore::STATUS_INVALID_DATA, "Name cannot be empty"));
+ }
+ if (nameSpace.empty()) {
+ return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
+ IIdentityCredentialStore::STATUS_INVALID_DATA,
+ "Name space cannot be empty"));
+ }
+
+ if (requestCountsRemaining_.size() == 0) {
+ return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
+ IIdentityCredentialStore::STATUS_INVALID_DATA,
+ "No more name spaces left to go through"));
+ }
+
+ bool newNamespace;
+ if (currentNameSpace_ == "") {
+ // First call.
+ currentNameSpace_ = nameSpace;
+ newNamespace = true;
+ }
+
+ if (nameSpace == currentNameSpace_) {
+ // Same namespace.
+ if (requestCountsRemaining_[0] == 0) {
+ return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
+ IIdentityCredentialStore::STATUS_INVALID_DATA,
+ "No more entries to be retrieved in current name space"));
+ }
+ requestCountsRemaining_[0] -= 1;
+ } else {
+ // New namespace.
+ if (requestCountsRemaining_[0] != 0) {
+ return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
+ IIdentityCredentialStore::STATUS_INVALID_DATA,
+ "Moved to new name space but one or more entries need to be "
+ "retrieved "
+ "in current name space"));
+ }
+ if (currentNameSpaceDeviceNameSpacesMap_.size() > 0) {
+ deviceNameSpacesMap_.add(currentNameSpace_,
+ std::move(currentNameSpaceDeviceNameSpacesMap_));
+ }
+ currentNameSpaceDeviceNameSpacesMap_ = cppbor::Map();
+
+ requestCountsRemaining_.erase(requestCountsRemaining_.begin());
+ currentNameSpace_ = nameSpace;
+ newNamespace = true;
+ }
+
+ // It's permissible to have an empty itemsRequest... but if non-empty you can
+ // only request what was specified in said itemsRequest. Enforce that.
+ if (itemsRequest_.size() > 0) {
+ const auto& it = requestedNameSpacesAndNames_.find(nameSpace);
+ if (it == requestedNameSpacesAndNames_.end()) {
+ return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
+ IIdentityCredentialStore::STATUS_NOT_IN_REQUEST_MESSAGE,
+ "Name space was not requested in startRetrieval"));
+ }
+ const set<string>& dataItemNames = it->second;
+ if (dataItemNames.find(name) == dataItemNames.end()) {
+ return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
+ IIdentityCredentialStore::STATUS_NOT_IN_REQUEST_MESSAGE,
+ "Data item name in name space was not requested in startRetrieval"));
+ }
+ }
+
+ unsigned int newNamespaceNumEntries = 0;
+ if (newNamespace) {
+ if (expectedNumEntriesPerNamespace_.size() == 0) {
+ return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
+ IIdentityCredentialStore::STATUS_INVALID_DATA,
+ "No more populated name spaces left to go through"));
+ }
+ newNamespaceNumEntries = expectedNumEntriesPerNamespace_[0];
+ expectedNumEntriesPerNamespace_.erase(
+ expectedNumEntriesPerNamespace_.begin());
+ }
+
+ // Access control is enforced in the secure hardware.
+ //
+ // ... except for STATUS_NOT_IN_REQUEST_MESSAGE, that's handled above (TODO:
+ // consolidate).
+ //
+ AccessCheckResult res =
+ hwProxy_->startRetrieveEntryValue(nameSpace, name, newNamespaceNumEntries,
+ entrySize, accessControlProfileIds);
+ switch (res) {
+ case AccessCheckResult::kOk:
+ /* Do nothing. */
+ break;
+ case AccessCheckResult::kFailed:
+ return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
+ IIdentityCredentialStore::STATUS_FAILED,
+ "Access control check failed (failed)"));
+ break;
+ case AccessCheckResult::kNoAccessControlProfiles:
+ return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
+ IIdentityCredentialStore::STATUS_NO_ACCESS_CONTROL_PROFILES,
+ "Access control check failed (no access control profiles)"));
+ break;
+ case AccessCheckResult::kUserAuthenticationFailed:
+ return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
+ IIdentityCredentialStore::STATUS_USER_AUTHENTICATION_FAILED,
+ "Access control check failed (user auth)"));
+ break;
+ case AccessCheckResult::kReaderAuthenticationFailed:
+ return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
+ IIdentityCredentialStore::STATUS_READER_AUTHENTICATION_FAILED,
+ "Access control check failed (reader auth)"));
+ break;
+ }
+
+ currentName_ = name;
+ currentAccessControlProfileIds_ = accessControlProfileIds;
+ entryRemainingBytes_ = entrySize;
+ entryValue_.resize(0);
+
+ return ndk::ScopedAStatus::ok();
+}
+
+ndk::ScopedAStatus IdentityCredential::retrieveEntryValue(
+ const vector<uint8_t>& encryptedContent, vector<uint8_t>* outContent) {
+ optional<vector<uint8_t>> content = hwProxy_->retrieveEntryValue(
+ encryptedContent, currentNameSpace_, currentName_,
+ currentAccessControlProfileIds_);
+ if (!content) {
+ return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
+ IIdentityCredentialStore::STATUS_INVALID_DATA,
+ "Error decrypting data"));
+ }
+
+ size_t chunkSize = content.value().size();
+
+ if (chunkSize > entryRemainingBytes_) {
+ LOG(ERROR) << "Retrieved chunk of size " << chunkSize
+ << " is bigger than remaining space of size "
+ << entryRemainingBytes_;
+ return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
+ IIdentityCredentialStore::STATUS_INVALID_DATA,
+ "Retrieved chunk is bigger than remaining space"));
+ }
+
+ entryRemainingBytes_ -= chunkSize;
+ if (entryRemainingBytes_ > 0) {
+ if (chunkSize != IdentityCredentialStore::kGcmChunkSize) {
+ return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
+ IIdentityCredentialStore::STATUS_INVALID_DATA,
+ "Retrieved non-final chunk of size which isn't kGcmChunkSize"));
+ }
+ }
+
+ entryValue_.insert(entryValue_.end(), content.value().begin(),
+ content.value().end());
+
+ if (entryRemainingBytes_ == 0) {
+ auto [entryValueItem, _, message] = cppbor::parse(entryValue_);
+ if (entryValueItem == nullptr) {
+ return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
+ IIdentityCredentialStore::STATUS_INVALID_DATA,
+ "Retrieved data which is invalid CBOR"));
+ }
+ currentNameSpaceDeviceNameSpacesMap_.add(currentName_,
+ std::move(entryValueItem));
+ }
+
+ *outContent = content.value();
+ return ndk::ScopedAStatus::ok();
+}
+
+ndk::ScopedAStatus IdentityCredential::finishRetrieval(
+ vector<uint8_t>* outMac, vector<uint8_t>* outDeviceNameSpaces) {
+ if (currentNameSpaceDeviceNameSpacesMap_.size() > 0) {
+ deviceNameSpacesMap_.add(currentNameSpace_,
+ std::move(currentNameSpaceDeviceNameSpacesMap_));
+ }
+ vector<uint8_t> encodedDeviceNameSpaces = deviceNameSpacesMap_.encode();
+
+ if (encodedDeviceNameSpaces.size() != expectedDeviceNameSpacesSize_) {
+ LOG(ERROR) << "encodedDeviceNameSpaces is "
+ << encodedDeviceNameSpaces.size() << " bytes, "
+ << "was expecting " << expectedDeviceNameSpacesSize_;
+ return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
+ IIdentityCredentialStore::STATUS_INVALID_DATA,
+ StringPrintf("Unexpected CBOR size %zd for encodedDeviceNameSpaces, "
+ "was expecting %zd",
+ encodedDeviceNameSpaces.size(),
+ expectedDeviceNameSpacesSize_)
+ .c_str()));
+ }
+
+ // If there's no signing key or no sessionTranscript or no reader ephemeral
+ // public key, we return the empty MAC.
+ optional<vector<uint8_t>> mac;
+ if (signingKeyBlob_.size() > 0 && sessionTranscript_.size() > 0 &&
+ readerPublicKey_.size() > 0) {
+ optional<vector<uint8_t>> digestToBeMaced = hwProxy_->finishRetrieval();
+ if (!digestToBeMaced || digestToBeMaced.value().size() != 32) {
+ return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
+ IIdentityCredentialStore::STATUS_INVALID_DATA,
+ "Error generating digestToBeMaced"));
+ }
+ // Now construct COSE_Mac0 from the returned MAC...
+ mac = support::coseMacWithDigest(digestToBeMaced.value(), {} /* data */);
+ }
+
+ *outMac = mac.value_or(vector<uint8_t>({}));
+ *outDeviceNameSpaces = encodedDeviceNameSpaces;
+ return ndk::ScopedAStatus::ok();
+}
+
+ndk::ScopedAStatus IdentityCredential::generateSigningKeyPair(
+ vector<uint8_t>* outSigningKeyBlob, Certificate* outSigningKeyCertificate) {
+ time_t now = time(NULL);
+ optional<pair<vector<uint8_t>, vector<uint8_t>>> pair =
+ hwProxy_->generateSigningKeyPair(docType_, now);
+ if (!pair) {
+ return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
+ IIdentityCredentialStore::STATUS_FAILED, "Error creating signingKey"));
+ }
+
+ *outSigningKeyCertificate = Certificate();
+ outSigningKeyCertificate->encodedCertificate = pair->first;
+
+ *outSigningKeyBlob = pair->second;
+ return ndk::ScopedAStatus::ok();
+}
+
+ndk::ScopedAStatus IdentityCredential::updateCredential(
+ shared_ptr<IWritableIdentityCredential>* outWritableCredential) {
+ sp<SecureHardwareProvisioningProxy> hwProxy =
+ hwProxyFactory_->createProvisioningProxy();
+ shared_ptr<WritableIdentityCredential> wc =
+ ndk::SharedRefBase::make<WritableIdentityCredential>(hwProxy, docType_,
+ testCredential_);
+ if (!wc->initializeForUpdate(encryptedCredentialKeys_)) {
+ return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
+ IIdentityCredentialStore::STATUS_FAILED,
+ "Error initializing WritableIdentityCredential for update"));
+ }
+ *outWritableCredential = wc;
+ return ndk::ScopedAStatus::ok();
+}
+
+} // namespace aidl::android::hardware::identity
diff --git a/guest/hals/identity/common/IdentityCredential.h b/guest/hals/identity/common/IdentityCredential.h
new file mode 100644
index 0000000..aaf772a
--- /dev/null
+++ b/guest/hals/identity/common/IdentityCredential.h
@@ -0,0 +1,153 @@
+/*
+ * Copyright 2019, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef ANDROID_HARDWARE_IDENTITY_IDENTITYCREDENTIAL_H
+#define ANDROID_HARDWARE_IDENTITY_IDENTITYCREDENTIAL_H
+
+#include <aidl/android/hardware/identity/BnIdentityCredential.h>
+#include <aidl/android/hardware/keymaster/HardwareAuthToken.h>
+#include <aidl/android/hardware/keymaster/VerificationToken.h>
+#include <android/hardware/identity/support/IdentityCredentialSupport.h>
+
+#include <map>
+#include <set>
+#include <string>
+#include <vector>
+
+#include <cppbor.h>
+
+#include "IdentityCredentialStore.h"
+#include "SecureHardwareProxy.h"
+
+namespace aidl::android::hardware::identity {
+
+using ::aidl::android::hardware::keymaster::HardwareAuthToken;
+using ::aidl::android::hardware::keymaster::VerificationToken;
+using ::android::sp;
+using ::android::hardware::identity::SecureHardwarePresentationProxy;
+using ::std::map;
+using ::std::set;
+using ::std::string;
+using ::std::vector;
+
+class IdentityCredential : public BnIdentityCredential {
+ public:
+ IdentityCredential(sp<SecureHardwareProxyFactory> hwProxyFactory,
+ sp<SecureHardwarePresentationProxy> hwProxy,
+ const vector<uint8_t>& credentialData)
+ : hwProxyFactory_(hwProxyFactory),
+ hwProxy_(hwProxy),
+ credentialData_(credentialData),
+ numStartRetrievalCalls_(0),
+ expectedDeviceNameSpacesSize_(0) {}
+
+ // Parses and decrypts credentialData_, return a status code from
+ // IIdentityCredentialStore. Must be called right after construction.
+ int initialize();
+
+ // Methods from IIdentityCredential follow.
+ ndk::ScopedAStatus deleteCredential(
+ vector<uint8_t>* outProofOfDeletionSignature) override;
+ ndk::ScopedAStatus deleteCredentialWithChallenge(
+ const vector<uint8_t>& challenge,
+ vector<uint8_t>* outProofOfDeletionSignature) override;
+ ndk::ScopedAStatus proveOwnership(
+ const vector<uint8_t>& challenge,
+ vector<uint8_t>* outProofOfOwnershipSignature) override;
+ ndk::ScopedAStatus createEphemeralKeyPair(
+ vector<uint8_t>* outKeyPair) override;
+ ndk::ScopedAStatus setReaderEphemeralPublicKey(
+ const vector<uint8_t>& publicKey) override;
+ ndk::ScopedAStatus createAuthChallenge(int64_t* outChallenge) override;
+ ndk::ScopedAStatus setRequestedNamespaces(
+ const vector<RequestNamespace>& requestNamespaces) override;
+ ndk::ScopedAStatus setVerificationToken(
+ const VerificationToken& verificationToken) override;
+ ndk::ScopedAStatus startRetrieval(
+ const vector<SecureAccessControlProfile>& accessControlProfiles,
+ const HardwareAuthToken& authToken, const vector<uint8_t>& itemsRequest,
+ const vector<uint8_t>& signingKeyBlob,
+ const vector<uint8_t>& sessionTranscript,
+ const vector<uint8_t>& readerSignature,
+ const vector<int32_t>& requestCounts) override;
+ ndk::ScopedAStatus startRetrieveEntryValue(
+ const string& nameSpace, const string& name, int32_t entrySize,
+ const vector<int32_t>& accessControlProfileIds) override;
+ ndk::ScopedAStatus retrieveEntryValue(const vector<uint8_t>& encryptedContent,
+ vector<uint8_t>* outContent) override;
+ ndk::ScopedAStatus finishRetrieval(
+ vector<uint8_t>* outMac, vector<uint8_t>* outDeviceNameSpaces) override;
+ ndk::ScopedAStatus generateSigningKeyPair(
+ vector<uint8_t>* outSigningKeyBlob,
+ Certificate* outSigningKeyCertificate) override;
+
+ ndk::ScopedAStatus updateCredential(
+ shared_ptr<IWritableIdentityCredential>* outWritableCredential) override;
+
+ private:
+ ndk::ScopedAStatus deleteCredentialCommon(
+ const vector<uint8_t>& challenge, bool includeChallenge,
+ vector<uint8_t>* outProofOfDeletionSignature);
+
+ // Set by constructor
+ sp<SecureHardwareProxyFactory> hwProxyFactory_;
+ sp<SecureHardwarePresentationProxy> hwProxy_;
+ vector<uint8_t> credentialData_;
+ int numStartRetrievalCalls_;
+
+ // Set by initialize()
+ string docType_;
+ bool testCredential_;
+ vector<uint8_t> encryptedCredentialKeys_;
+
+ // Set by createEphemeralKeyPair()
+ vector<uint8_t> ephemeralPublicKey_;
+
+ // Set by setReaderEphemeralPublicKey()
+ vector<uint8_t> readerPublicKey_;
+
+ // Set by setRequestedNamespaces()
+ vector<RequestNamespace> requestNamespaces_;
+
+ // Set by setVerificationToken().
+ VerificationToken verificationToken_;
+
+ // Set at startRetrieval() time.
+ vector<uint8_t> signingKeyBlob_;
+ vector<uint8_t> sessionTranscript_;
+ vector<uint8_t> itemsRequest_;
+ vector<int32_t> requestCountsRemaining_;
+ map<string, set<string>> requestedNameSpacesAndNames_;
+ cppbor::Map deviceNameSpacesMap_;
+ cppbor::Map currentNameSpaceDeviceNameSpacesMap_;
+
+ // Calculated at startRetrieval() time.
+ size_t expectedDeviceNameSpacesSize_;
+ vector<unsigned int> expectedNumEntriesPerNamespace_;
+
+ // Set at startRetrieveEntryValue() time.
+ string currentNameSpace_;
+ string currentName_;
+ vector<int32_t> currentAccessControlProfileIds_;
+ size_t entryRemainingBytes_;
+ vector<uint8_t> entryValue_;
+
+ void calcDeviceNameSpacesSize(uint32_t accessControlProfileMask);
+};
+
+} // namespace aidl::android::hardware::identity
+
+#endif // ANDROID_HARDWARE_IDENTITY_IDENTITYCREDENTIAL_H
diff --git a/guest/hals/identity/common/IdentityCredentialStore.cpp b/guest/hals/identity/common/IdentityCredentialStore.cpp
new file mode 100644
index 0000000..0847c0c
--- /dev/null
+++ b/guest/hals/identity/common/IdentityCredentialStore.cpp
@@ -0,0 +1,86 @@
+/*
+ * Copyright 2019, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#define LOG_TAG "IdentityCredentialStore"
+
+#include <android-base/logging.h>
+
+#include "IdentityCredential.h"
+#include "IdentityCredentialStore.h"
+#include "WritableIdentityCredential.h"
+
+namespace aidl::android::hardware::identity {
+
+using ::aidl::android::hardware::security::keymint::
+ IRemotelyProvisionedComponent;
+
+ndk::ScopedAStatus IdentityCredentialStore::getHardwareInformation(
+ HardwareInformation* hardwareInformation) {
+ HardwareInformation hw;
+ hw.credentialStoreName =
+ "Identity Credential Cuttlefish Remote Implementation";
+ hw.credentialStoreAuthorName = "Google";
+ hw.dataChunkSize = kGcmChunkSize;
+ hw.isDirectAccess = false;
+ hw.supportedDocTypes = {};
+ *hardwareInformation = hw;
+ return ndk::ScopedAStatus::ok();
+}
+
+ndk::ScopedAStatus IdentityCredentialStore::createCredential(
+ const string& docType, bool testCredential,
+ shared_ptr<IWritableIdentityCredential>* outWritableCredential) {
+ sp<SecureHardwareProvisioningProxy> hwProxy =
+ hwProxyFactory_->createProvisioningProxy();
+ shared_ptr<WritableIdentityCredential> wc =
+ ndk::SharedRefBase::make<WritableIdentityCredential>(hwProxy, docType,
+ testCredential);
+ if (!wc->initialize()) {
+ return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
+ IIdentityCredentialStore::STATUS_FAILED,
+ "Error initializing WritableIdentityCredential"));
+ }
+ *outWritableCredential = wc;
+ return ndk::ScopedAStatus::ok();
+}
+
+ndk::ScopedAStatus IdentityCredentialStore::getCredential(
+ CipherSuite cipherSuite, const vector<uint8_t>& credentialData,
+ shared_ptr<IIdentityCredential>* outCredential) {
+ // We only support CIPHERSUITE_ECDHE_HKDF_ECDSA_WITH_AES_256_GCM_SHA256 right
+ // now.
+ if (cipherSuite !=
+ CipherSuite::CIPHERSUITE_ECDHE_HKDF_ECDSA_WITH_AES_256_GCM_SHA256) {
+ return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
+ IIdentityCredentialStore::STATUS_CIPHER_SUITE_NOT_SUPPORTED,
+ "Unsupported cipher suite"));
+ }
+
+ sp<SecureHardwarePresentationProxy> hwProxy =
+ hwProxyFactory_->createPresentationProxy();
+ shared_ptr<IdentityCredential> credential =
+ ndk::SharedRefBase::make<IdentityCredential>(hwProxyFactory_, hwProxy,
+ credentialData);
+ auto ret = credential->initialize();
+ if (ret != IIdentityCredentialStore::STATUS_OK) {
+ return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
+ int(ret), "Error initializing IdentityCredential"));
+ }
+ *outCredential = credential;
+ return ndk::ScopedAStatus::ok();
+}
+
+} // namespace aidl::android::hardware::identity
diff --git a/guest/hals/identity/common/IdentityCredentialStore.h b/guest/hals/identity/common/IdentityCredentialStore.h
new file mode 100644
index 0000000..1d65b2c
--- /dev/null
+++ b/guest/hals/identity/common/IdentityCredentialStore.h
@@ -0,0 +1,59 @@
+/*
+ * Copyright 2019, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef ANDROID_HARDWARE_IDENTITY_IDENTITYCREDENTIALSTORE_H
+#define ANDROID_HARDWARE_IDENTITY_IDENTITYCREDENTIALSTORE_H
+
+#include <aidl/android/hardware/identity/BnIdentityCredentialStore.h>
+#include <aidl/android/hardware/security/keymint/IRemotelyProvisionedComponent.h>
+
+#include "SecureHardwareProxy.h"
+
+namespace aidl::android::hardware::identity {
+
+using ::android::sp;
+using ::android::hardware::identity::SecureHardwareProxyFactory;
+using ::std::shared_ptr;
+using ::std::string;
+using ::std::vector;
+
+class IdentityCredentialStore : public BnIdentityCredentialStore {
+ public:
+ IdentityCredentialStore(sp<SecureHardwareProxyFactory> hwProxyFactory)
+ : hwProxyFactory_(hwProxyFactory) {}
+
+ // The GCM chunk size used by this implementation is 64 KiB.
+ static constexpr size_t kGcmChunkSize = 64 * 1024;
+
+ // Methods from IIdentityCredentialStore follow.
+ ndk::ScopedAStatus getHardwareInformation(
+ HardwareInformation* hardwareInformation) override;
+
+ ndk::ScopedAStatus createCredential(
+ const string& docType, bool testCredential,
+ shared_ptr<IWritableIdentityCredential>* outWritableCredential) override;
+
+ ndk::ScopedAStatus getCredential(
+ CipherSuite cipherSuite, const vector<uint8_t>& credentialData,
+ shared_ptr<IIdentityCredential>* outCredential) override;
+
+ private:
+ sp<SecureHardwareProxyFactory> hwProxyFactory_;
+};
+
+} // namespace aidl::android::hardware::identity
+
+#endif // ANDROID_HARDWARE_IDENTITY_IDENTITYCREDENTIALSTORE_H
diff --git a/guest/hals/identity/common/SecureHardwareProxy.h b/guest/hals/identity/common/SecureHardwareProxy.h
new file mode 100644
index 0000000..bd252a7
--- /dev/null
+++ b/guest/hals/identity/common/SecureHardwareProxy.h
@@ -0,0 +1,191 @@
+/*
+ * Copyright 2020, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef ANDROID_HARDWARE_IDENTITY_SECUREHARDWAREPROXY_H
+#define ANDROID_HARDWARE_IDENTITY_SECUREHARDWAREPROXY_H
+
+#include <utils/RefBase.h>
+#include <optional>
+#include <string>
+#include <utility>
+#include <vector>
+
+namespace android::hardware::identity {
+
+using ::android::RefBase;
+using ::std::optional;
+using ::std::pair;
+using ::std::string;
+using ::std::vector;
+
+// These classes are used to communicate with Secure Hardware. They mimic the
+// API in libEmbeddedIC 1:1 (except for using C++ types) as each call is
+// intended to be forwarded to the Secure Hardware.
+//
+// Instances are instantiated when a provisioning or presentation session
+// starts. When the session is complete, the shutdown() method is called.
+//
+
+// Forward declare.
+//
+class SecureHardwareProvisioningProxy;
+class SecureHardwarePresentationProxy;
+
+// This is a class used to create proxies.
+//
+class SecureHardwareProxyFactory : public RefBase {
+ public:
+ SecureHardwareProxyFactory() {}
+ virtual ~SecureHardwareProxyFactory() {}
+
+ virtual sp<SecureHardwareProvisioningProxy> createProvisioningProxy() = 0;
+ virtual sp<SecureHardwarePresentationProxy> createPresentationProxy() = 0;
+};
+
+// The proxy used for provisioning.
+//
+class SecureHardwareProvisioningProxy : public RefBase {
+ public:
+ SecureHardwareProvisioningProxy() {}
+ virtual ~SecureHardwareProvisioningProxy() {}
+
+ virtual bool initialize(bool testCredential) = 0;
+
+ virtual bool initializeForUpdate(bool testCredential, string docType,
+ vector<uint8_t> encryptedCredentialKeys) = 0;
+
+ // Returns public key certificate chain with attestation.
+ //
+ // This must return an entire certificate chain and its implementation must
+ // be coordinated with the implementation of eicOpsCreateCredentialKey() on
+ // the TA side (which may return just a single certificate or the entire
+ // chain).
+ virtual optional<vector<uint8_t>> createCredentialKey(
+ const vector<uint8_t>& challenge,
+ const vector<uint8_t>& applicationId) = 0;
+
+ virtual bool startPersonalization(int accessControlProfileCount,
+ vector<int> entryCounts,
+ const string& docType,
+ size_t expectedProofOfProvisioningSize) = 0;
+
+ // Returns MAC (28 bytes).
+ virtual optional<vector<uint8_t>> addAccessControlProfile(
+ int id, const vector<uint8_t>& readerCertificate,
+ bool userAuthenticationRequired, uint64_t timeoutMillis,
+ uint64_t secureUserId) = 0;
+
+ virtual bool beginAddEntry(const vector<int>& accessControlProfileIds,
+ const string& nameSpace, const string& name,
+ uint64_t entrySize) = 0;
+
+ // Returns encryptedContent.
+ virtual optional<vector<uint8_t>> addEntryValue(
+ const vector<int>& accessControlProfileIds, const string& nameSpace,
+ const string& name, const vector<uint8_t>& content) = 0;
+
+ // Returns signatureOfToBeSigned (EIC_ECDSA_P256_SIGNATURE_SIZE bytes).
+ virtual optional<vector<uint8_t>> finishAddingEntries() = 0;
+
+ // Returns encryptedCredentialKeys (80 bytes).
+ virtual optional<vector<uint8_t>> finishGetCredentialData(
+ const string& docType) = 0;
+
+ virtual bool shutdown() = 0;
+};
+
+enum AccessCheckResult {
+ kOk,
+ kFailed,
+ kNoAccessControlProfiles,
+ kUserAuthenticationFailed,
+ kReaderAuthenticationFailed,
+};
+
+// The proxy used for presentation.
+//
+class SecureHardwarePresentationProxy : public RefBase {
+ public:
+ SecureHardwarePresentationProxy() {}
+ virtual ~SecureHardwarePresentationProxy() {}
+
+ virtual bool initialize(bool testCredential, string docType,
+ vector<uint8_t> encryptedCredentialKeys) = 0;
+
+ // Returns publicKeyCert (1st component) and signingKeyBlob (2nd component)
+ virtual optional<pair<vector<uint8_t>, vector<uint8_t>>>
+ generateSigningKeyPair(string docType, time_t now) = 0;
+
+ // Returns private key
+ virtual optional<vector<uint8_t>> createEphemeralKeyPair() = 0;
+
+ virtual optional<uint64_t> createAuthChallenge() = 0;
+
+ virtual bool startRetrieveEntries() = 0;
+
+ virtual bool setAuthToken(uint64_t challenge, uint64_t secureUserId,
+ uint64_t authenticatorId,
+ int hardwareAuthenticatorType, uint64_t timeStamp,
+ const vector<uint8_t>& mac,
+ uint64_t verificationTokenChallenge,
+ uint64_t verificationTokenTimestamp,
+ int verificationTokenSecurityLevel,
+ const vector<uint8_t>& verificationTokenMac) = 0;
+
+ virtual bool pushReaderCert(const vector<uint8_t>& certX509) = 0;
+
+ virtual optional<bool> validateAccessControlProfile(
+ int id, const vector<uint8_t>& readerCertificate,
+ bool userAuthenticationRequired, int timeoutMillis, uint64_t secureUserId,
+ const vector<uint8_t>& mac) = 0;
+
+ virtual bool validateRequestMessage(
+ const vector<uint8_t>& sessionTranscript,
+ const vector<uint8_t>& requestMessage, int coseSignAlg,
+ const vector<uint8_t>& readerSignatureOfToBeSigned) = 0;
+
+ virtual bool calcMacKey(const vector<uint8_t>& sessionTranscript,
+ const vector<uint8_t>& readerEphemeralPublicKey,
+ const vector<uint8_t>& signingKeyBlob,
+ const string& docType,
+ unsigned int numNamespacesWithValues,
+ size_t expectedProofOfProvisioningSize) = 0;
+
+ virtual AccessCheckResult startRetrieveEntryValue(
+ const string& nameSpace, const string& name,
+ unsigned int newNamespaceNumEntries, int32_t entrySize,
+ const vector<int32_t>& accessControlProfileIds) = 0;
+
+ virtual optional<vector<uint8_t>> retrieveEntryValue(
+ const vector<uint8_t>& encryptedContent, const string& nameSpace,
+ const string& name, const vector<int32_t>& accessControlProfileIds) = 0;
+
+ virtual optional<vector<uint8_t>> finishRetrieval();
+
+ virtual optional<vector<uint8_t>> deleteCredential(
+ const string& docType, const vector<uint8_t>& challenge,
+ bool includeChallenge, size_t proofOfDeletionCborSize) = 0;
+
+ virtual optional<vector<uint8_t>> proveOwnership(
+ const string& docType, bool testCredential,
+ const vector<uint8_t>& challenge, size_t proofOfOwnershipCborSize) = 0;
+
+ virtual bool shutdown() = 0;
+};
+
+} // namespace android::hardware::identity
+
+#endif // ANDROID_HARDWARE_IDENTITY_SECUREHARDWAREPROXY_H
diff --git a/guest/hals/identity/common/WritableIdentityCredential.cpp b/guest/hals/identity/common/WritableIdentityCredential.cpp
new file mode 100644
index 0000000..13ee244
--- /dev/null
+++ b/guest/hals/identity/common/WritableIdentityCredential.cpp
@@ -0,0 +1,424 @@
+/*
+ * Copyright 2019, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#define LOG_TAG "WritableIdentityCredential"
+
+#include "WritableIdentityCredential.h"
+
+#include <android/hardware/identity/support/IdentityCredentialSupport.h>
+
+#include <android-base/logging.h>
+#include <android-base/stringprintf.h>
+
+#include <cppbor.h>
+#include <cppbor_parse.h>
+
+#include <utility>
+
+#include "IdentityCredentialStore.h"
+
+#include "SecureHardwareProxy.h"
+
+namespace aidl::android::hardware::identity {
+
+using ::android::base::StringPrintf;
+using ::std::optional;
+using namespace ::android::hardware::identity;
+
+bool WritableIdentityCredential::initialize() {
+ if (!hwProxy_->initialize(testCredential_)) {
+ LOG(ERROR) << "hwProxy->initialize() failed";
+ return false;
+ }
+ startPersonalizationCalled_ = false;
+ firstEntry_ = true;
+
+ return true;
+}
+
+// Used when updating a credential. Returns false on failure.
+bool WritableIdentityCredential::initializeForUpdate(
+ const vector<uint8_t>& encryptedCredentialKeys) {
+ if (!hwProxy_->initializeForUpdate(testCredential_, docType_,
+ encryptedCredentialKeys)) {
+ LOG(ERROR) << "hwProxy->initializeForUpdate() failed";
+ return false;
+ }
+ startPersonalizationCalled_ = false;
+ firstEntry_ = true;
+
+ return true;
+}
+
+WritableIdentityCredential::~WritableIdentityCredential() {}
+
+ndk::ScopedAStatus WritableIdentityCredential::getAttestationCertificate(
+ const vector<uint8_t>& attestationApplicationId,
+ const vector<uint8_t>& attestationChallenge,
+ vector<Certificate>* outCertificateChain) {
+ if (getAttestationCertificateAlreadyCalled_) {
+ return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
+ IIdentityCredentialStore::STATUS_FAILED,
+ "Error attestation certificate previously generated"));
+ }
+ getAttestationCertificateAlreadyCalled_ = true;
+
+ if (attestationChallenge.empty()) {
+ return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
+ IIdentityCredentialStore::STATUS_INVALID_DATA,
+ "Challenge can not be empty"));
+ }
+
+ optional<vector<uint8_t>> certChain = hwProxy_->createCredentialKey(
+ attestationChallenge, attestationApplicationId);
+ if (!certChain) {
+ return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
+ IIdentityCredentialStore::STATUS_FAILED,
+ "Error generating attestation certificate chain"));
+ }
+
+ optional<vector<vector<uint8_t>>> certs =
+ support::certificateChainSplit(certChain.value());
+ if (!certs) {
+ return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
+ IIdentityCredentialStore::STATUS_FAILED,
+ "Error splitting chain into separate certificates"));
+ }
+
+ *outCertificateChain = vector<Certificate>();
+ for (const vector<uint8_t>& cert : certs.value()) {
+ Certificate c = Certificate();
+ c.encodedCertificate = cert;
+ outCertificateChain->push_back(std::move(c));
+ }
+
+ return ndk::ScopedAStatus::ok();
+}
+
+ndk::ScopedAStatus
+WritableIdentityCredential::setExpectedProofOfProvisioningSize(
+ int32_t expectedProofOfProvisioningSize) {
+ expectedProofOfProvisioningSize_ = expectedProofOfProvisioningSize;
+ return ndk::ScopedAStatus::ok();
+}
+
+ndk::ScopedAStatus WritableIdentityCredential::startPersonalization(
+ int32_t accessControlProfileCount, const vector<int32_t>& entryCounts) {
+ if (startPersonalizationCalled_) {
+ return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
+ IIdentityCredentialStore::STATUS_FAILED,
+ "startPersonalization called already"));
+ }
+ startPersonalizationCalled_ = true;
+
+ numAccessControlProfileRemaining_ = accessControlProfileCount;
+ remainingEntryCounts_ = entryCounts;
+ entryNameSpace_ = "";
+
+ signedDataAccessControlProfiles_ = cppbor::Array();
+ signedDataNamespaces_ = cppbor::Map();
+ signedDataCurrentNamespace_ = cppbor::Array();
+
+ if (!hwProxy_->startPersonalization(accessControlProfileCount, entryCounts,
+ docType_,
+ expectedProofOfProvisioningSize_)) {
+ return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
+ IIdentityCredentialStore::STATUS_FAILED, "eicStartPersonalization"));
+ }
+
+ return ndk::ScopedAStatus::ok();
+}
+
+ndk::ScopedAStatus WritableIdentityCredential::addAccessControlProfile(
+ int32_t id, const Certificate& readerCertificate,
+ bool userAuthenticationRequired, int64_t timeoutMillis,
+ int64_t secureUserId,
+ SecureAccessControlProfile* outSecureAccessControlProfile) {
+ if (numAccessControlProfileRemaining_ == 0) {
+ return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
+ IIdentityCredentialStore::STATUS_INVALID_DATA,
+ "numAccessControlProfileRemaining_ is 0 and expected non-zero"));
+ }
+
+ if (accessControlProfileIds_.find(id) != accessControlProfileIds_.end()) {
+ return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
+ IIdentityCredentialStore::STATUS_INVALID_DATA,
+ "Access Control Profile id must be unique"));
+ }
+ accessControlProfileIds_.insert(id);
+
+ if (id < 0 || id >= 32) {
+ return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
+ IIdentityCredentialStore::STATUS_INVALID_DATA,
+ "Access Control Profile id must be non-negative and less than 32"));
+ }
+
+ // Spec requires if |userAuthenticationRequired| is false, then
+ // |timeoutMillis| must also be zero.
+ if (!userAuthenticationRequired && timeoutMillis != 0) {
+ return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
+ IIdentityCredentialStore::STATUS_INVALID_DATA,
+ "userAuthenticationRequired is false but timeout is non-zero"));
+ }
+
+ optional<vector<uint8_t>> mac = hwProxy_->addAccessControlProfile(
+ id, readerCertificate.encodedCertificate, userAuthenticationRequired,
+ timeoutMillis, secureUserId);
+ if (!mac) {
+ return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
+ IIdentityCredentialStore::STATUS_FAILED, "eicAddAccessControlProfile"));
+ }
+
+ SecureAccessControlProfile profile;
+ profile.id = id;
+ profile.readerCertificate = readerCertificate;
+ profile.userAuthenticationRequired = userAuthenticationRequired;
+ profile.timeoutMillis = timeoutMillis;
+ profile.secureUserId = secureUserId;
+ profile.mac = mac.value();
+ cppbor::Map profileMap;
+ profileMap.add("id", profile.id);
+ if (profile.readerCertificate.encodedCertificate.size() > 0) {
+ profileMap.add("readerCertificate",
+ cppbor::Bstr(profile.readerCertificate.encodedCertificate));
+ }
+ if (profile.userAuthenticationRequired) {
+ profileMap.add("userAuthenticationRequired",
+ profile.userAuthenticationRequired);
+ profileMap.add("timeoutMillis", profile.timeoutMillis);
+ }
+ signedDataAccessControlProfiles_.add(std::move(profileMap));
+
+ numAccessControlProfileRemaining_--;
+
+ *outSecureAccessControlProfile = profile;
+ return ndk::ScopedAStatus::ok();
+}
+
+ndk::ScopedAStatus WritableIdentityCredential::beginAddEntry(
+ const vector<int32_t>& accessControlProfileIds, const string& nameSpace,
+ const string& name, int32_t entrySize) {
+ if (numAccessControlProfileRemaining_ != 0) {
+ LOG(ERROR) << "numAccessControlProfileRemaining_ is "
+ << numAccessControlProfileRemaining_ << " and expected zero";
+ return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
+ IIdentityCredentialStore::STATUS_INVALID_DATA,
+ "numAccessControlProfileRemaining_ is not zero"));
+ }
+
+ // Ensure passed-in profile ids reference valid access control profiles
+ for (const int32_t id : accessControlProfileIds) {
+ if (accessControlProfileIds_.find(id) == accessControlProfileIds_.end()) {
+ return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
+ IIdentityCredentialStore::STATUS_INVALID_DATA,
+ "An id in accessControlProfileIds references non-existing ACP"));
+ }
+ }
+
+ if (remainingEntryCounts_.size() == 0) {
+ return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
+ IIdentityCredentialStore::STATUS_INVALID_DATA,
+ "No more namespaces to add to"));
+ }
+
+ // Handle initial beginEntry() call.
+ if (firstEntry_) {
+ firstEntry_ = false;
+ entryNameSpace_ = nameSpace;
+ allNameSpaces_.insert(nameSpace);
+ }
+
+ // If the namespace changed...
+ if (nameSpace != entryNameSpace_) {
+ if (allNameSpaces_.find(nameSpace) != allNameSpaces_.end()) {
+ return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
+ IIdentityCredentialStore::STATUS_INVALID_DATA,
+ "Name space cannot be added in interleaving fashion"));
+ }
+
+ // Then check that all entries in the previous namespace have been added..
+ if (remainingEntryCounts_[0] != 0) {
+ return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
+ IIdentityCredentialStore::STATUS_INVALID_DATA,
+ "New namespace but a non-zero number of entries remain to be added"));
+ }
+ remainingEntryCounts_.erase(remainingEntryCounts_.begin());
+ remainingEntryCounts_[0] -= 1;
+ allNameSpaces_.insert(nameSpace);
+
+ if (signedDataCurrentNamespace_.size() > 0) {
+ signedDataNamespaces_.add(entryNameSpace_,
+ std::move(signedDataCurrentNamespace_));
+ signedDataCurrentNamespace_ = cppbor::Array();
+ }
+ } else {
+ // Same namespace...
+ if (remainingEntryCounts_[0] == 0) {
+ return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
+ IIdentityCredentialStore::STATUS_INVALID_DATA,
+ "Same namespace but no entries remain to be added"));
+ }
+ remainingEntryCounts_[0] -= 1;
+ }
+
+ entryRemainingBytes_ = entrySize;
+ entryNameSpace_ = nameSpace;
+ entryName_ = name;
+ entryAccessControlProfileIds_ = accessControlProfileIds;
+ entryBytes_.resize(0);
+ // LOG(INFO) << "name=" << name << " entrySize=" << entrySize;
+
+ if (!hwProxy_->beginAddEntry(accessControlProfileIds, nameSpace, name,
+ entrySize)) {
+ return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
+ IIdentityCredentialStore::STATUS_FAILED, "eicBeginAddEntry"));
+ }
+
+ return ndk::ScopedAStatus::ok();
+}
+
+ndk::ScopedAStatus WritableIdentityCredential::addEntryValue(
+ const vector<uint8_t>& content, vector<uint8_t>* outEncryptedContent) {
+ size_t contentSize = content.size();
+
+ if (contentSize > IdentityCredentialStore::kGcmChunkSize) {
+ return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
+ IIdentityCredentialStore::STATUS_INVALID_DATA,
+ "Passed in chunk of is bigger than kGcmChunkSize"));
+ }
+ if (contentSize > entryRemainingBytes_) {
+ return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
+ IIdentityCredentialStore::STATUS_INVALID_DATA,
+ "Passed in chunk is bigger than remaining space"));
+ }
+
+ entryBytes_.insert(entryBytes_.end(), content.begin(), content.end());
+ entryRemainingBytes_ -= contentSize;
+ if (entryRemainingBytes_ > 0) {
+ if (contentSize != IdentityCredentialStore::kGcmChunkSize) {
+ return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
+ IIdentityCredentialStore::STATUS_INVALID_DATA,
+ "Retrieved non-final chunk which isn't kGcmChunkSize"));
+ }
+ }
+
+ optional<vector<uint8_t>> encryptedContent = hwProxy_->addEntryValue(
+ entryAccessControlProfileIds_, entryNameSpace_, entryName_, content);
+ if (!encryptedContent) {
+ return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
+ IIdentityCredentialStore::STATUS_FAILED, "eicAddEntryValue"));
+ }
+
+ if (entryRemainingBytes_ == 0) {
+ // TODO: ideally do do this without parsing the data (but still validate
+ // data is valid CBOR).
+ auto [item, _, message] = cppbor::parse(entryBytes_);
+ if (item == nullptr) {
+ return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
+ IIdentityCredentialStore::STATUS_INVALID_DATA,
+ "Data is not valid CBOR"));
+ }
+ cppbor::Map entryMap;
+ entryMap.add("name", entryName_);
+ entryMap.add("value", std::move(item));
+ cppbor::Array profileIdArray;
+ for (auto id : entryAccessControlProfileIds_) {
+ profileIdArray.add(id);
+ }
+ entryMap.add("accessControlProfiles", std::move(profileIdArray));
+ signedDataCurrentNamespace_.add(std::move(entryMap));
+ }
+
+ *outEncryptedContent = encryptedContent.value();
+ return ndk::ScopedAStatus::ok();
+}
+
+ndk::ScopedAStatus WritableIdentityCredential::finishAddingEntries(
+ vector<uint8_t>* outCredentialData,
+ vector<uint8_t>* outProofOfProvisioningSignature) {
+ if (numAccessControlProfileRemaining_ != 0) {
+ return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
+ IIdentityCredentialStore::STATUS_INVALID_DATA,
+ "numAccessControlProfileRemaining_ is not 0 and expected zero"));
+ }
+
+ if (remainingEntryCounts_.size() > 1 || remainingEntryCounts_[0] != 0) {
+ return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
+ IIdentityCredentialStore::STATUS_INVALID_DATA,
+ "More entry spaces remain than startPersonalization configured"));
+ }
+
+ if (signedDataCurrentNamespace_.size() > 0) {
+ signedDataNamespaces_.add(entryNameSpace_,
+ std::move(signedDataCurrentNamespace_));
+ }
+ cppbor::Array popArray;
+ popArray.add("ProofOfProvisioning")
+ .add(docType_)
+ .add(std::move(signedDataAccessControlProfiles_))
+ .add(std::move(signedDataNamespaces_))
+ .add(testCredential_);
+ vector<uint8_t> encodedCbor = popArray.encode();
+
+ if (encodedCbor.size() != expectedProofOfProvisioningSize_) {
+ LOG(ERROR) << "CBOR for proofOfProvisioning is " << encodedCbor.size()
+ << " bytes, "
+ << "was expecting " << expectedProofOfProvisioningSize_;
+ return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
+ IIdentityCredentialStore::STATUS_INVALID_DATA,
+ StringPrintf("Unexpected CBOR size %zd for proofOfProvisioning, was "
+ "expecting %zd",
+ encodedCbor.size(), expectedProofOfProvisioningSize_)
+ .c_str()));
+ }
+
+ optional<vector<uint8_t>> signatureOfToBeSigned =
+ hwProxy_->finishAddingEntries();
+ if (!signatureOfToBeSigned) {
+ return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
+ IIdentityCredentialStore::STATUS_FAILED, "eicFinishAddingEntries"));
+ }
+
+ optional<vector<uint8_t>> signature =
+ support::coseSignEcDsaWithSignature(signatureOfToBeSigned.value(),
+ encodedCbor, // data
+ {}); // certificateChain
+ if (!signature) {
+ return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
+ IIdentityCredentialStore::STATUS_FAILED, "Error signing data"));
+ }
+
+ optional<vector<uint8_t>> encryptedCredentialKeys =
+ hwProxy_->finishGetCredentialData(docType_);
+ if (!encryptedCredentialKeys) {
+ return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
+ IIdentityCredentialStore::STATUS_FAILED,
+ "Error generating encrypted CredentialKeys"));
+ }
+ cppbor::Array array;
+ array.add(docType_);
+ array.add(testCredential_);
+ array.add(encryptedCredentialKeys.value());
+ vector<uint8_t> credentialData = array.encode();
+
+ *outCredentialData = credentialData;
+ *outProofOfProvisioningSignature = signature.value();
+ hwProxy_->shutdown();
+
+ return ndk::ScopedAStatus::ok();
+}
+
+} // namespace aidl::android::hardware::identity
diff --git a/guest/hals/identity/common/WritableIdentityCredential.h b/guest/hals/identity/common/WritableIdentityCredential.h
new file mode 100644
index 0000000..47a22ad
--- /dev/null
+++ b/guest/hals/identity/common/WritableIdentityCredential.h
@@ -0,0 +1,121 @@
+/*
+ * Copyright 2019, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef ANDROID_HARDWARE_IDENTITY_WRITABLEIDENTITYCREDENTIAL_H
+#define ANDROID_HARDWARE_IDENTITY_WRITABLEIDENTITYCREDENTIAL_H
+
+#include <aidl/android/hardware/identity/BnWritableIdentityCredential.h>
+#include <android/hardware/identity/support/IdentityCredentialSupport.h>
+
+#include <cppbor.h>
+#include <set>
+
+#include "IdentityCredentialStore.h"
+#include "SecureHardwareProxy.h"
+
+namespace aidl::android::hardware::identity {
+
+using ::android::sp;
+using ::android::hardware::identity::SecureHardwareProvisioningProxy;
+using ::std::set;
+using ::std::string;
+using ::std::vector;
+
+class WritableIdentityCredential : public BnWritableIdentityCredential {
+ public:
+ // For a new credential, call initialize() right after construction.
+ //
+ // For an updated credential, call initializeForUpdate() right after
+ // construction.
+ //
+ WritableIdentityCredential(sp<SecureHardwareProvisioningProxy> hwProxy,
+ const string& docType, bool testCredential)
+ : hwProxy_(hwProxy), docType_(docType), testCredential_(testCredential) {}
+
+ ~WritableIdentityCredential();
+
+ // Creates the Credential Key. Returns false on failure.
+ bool initialize();
+
+ // Used when updating a credential. Returns false on failure.
+ bool initializeForUpdate(const vector<uint8_t>& encryptedCredentialKeys);
+
+ // Methods from IWritableIdentityCredential follow.
+ ndk::ScopedAStatus getAttestationCertificate(
+ const vector<uint8_t>& attestationApplicationId,
+ const vector<uint8_t>& attestationChallenge,
+ vector<Certificate>* outCertificateChain) override;
+
+ ndk::ScopedAStatus setExpectedProofOfProvisioningSize(
+ int32_t expectedProofOfProvisioningSize) override;
+
+ ndk::ScopedAStatus startPersonalization(
+ int32_t accessControlProfileCount,
+ const vector<int32_t>& entryCounts) override;
+
+ ndk::ScopedAStatus addAccessControlProfile(
+ int32_t id, const Certificate& readerCertificate,
+ bool userAuthenticationRequired, int64_t timeoutMillis,
+ int64_t secureUserId,
+ SecureAccessControlProfile* outSecureAccessControlProfile) override;
+
+ ndk::ScopedAStatus beginAddEntry(
+ const vector<int32_t>& accessControlProfileIds, const string& nameSpace,
+ const string& name, int32_t entrySize) override;
+ ndk::ScopedAStatus addEntryValue(
+ const vector<uint8_t>& content,
+ vector<uint8_t>* outEncryptedContent) override;
+
+ ndk::ScopedAStatus finishAddingEntries(
+ vector<uint8_t>* outCredentialData,
+ vector<uint8_t>* outProofOfProvisioningSignature) override;
+
+ private:
+ // Set by constructor.
+ sp<SecureHardwareProvisioningProxy> hwProxy_;
+ string docType_;
+ bool testCredential_;
+
+ // This is set in initialize().
+ bool startPersonalizationCalled_;
+ bool firstEntry_;
+
+ // This is set in getAttestationCertificate().
+ bool getAttestationCertificateAlreadyCalled_ = false;
+
+ // These fields are initialized during startPersonalization()
+ size_t numAccessControlProfileRemaining_;
+ vector<int32_t> remainingEntryCounts_;
+ cppbor::Array signedDataAccessControlProfiles_;
+ cppbor::Map signedDataNamespaces_;
+ cppbor::Array signedDataCurrentNamespace_;
+ size_t expectedProofOfProvisioningSize_;
+
+ // This field is initialized in addAccessControlProfile
+ set<int32_t> accessControlProfileIds_;
+
+ // These fields are initialized during beginAddEntry()
+ size_t entryRemainingBytes_;
+ string entryNameSpace_;
+ string entryName_;
+ vector<int32_t> entryAccessControlProfileIds_;
+ vector<uint8_t> entryBytes_;
+ set<string> allNameSpaces_;
+};
+
+} // namespace aidl::android::hardware::identity
+
+#endif // ANDROID_HARDWARE_IDENTITY_WRITABLEIDENTITYCREDENTIAL_H
diff --git a/guest/hals/identity/libeic/EicCbor.c b/guest/hals/identity/libeic/EicCbor.c
new file mode 100644
index 0000000..b496342
--- /dev/null
+++ b/guest/hals/identity/libeic/EicCbor.c
@@ -0,0 +1,256 @@
+/*
+ * Copyright 2020, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "EicCbor.h"
+
+void eicCborInit(EicCbor* cbor, uint8_t* buffer, size_t bufferSize) {
+ eicMemSet(cbor, '\0', sizeof(EicCbor));
+ cbor->size = 0;
+ cbor->bufferSize = bufferSize;
+ cbor->buffer = buffer;
+ cbor->digestType = EIC_CBOR_DIGEST_TYPE_SHA256;
+ eicOpsSha256Init(&cbor->digester.sha256);
+}
+
+void eicCborInitHmacSha256(EicCbor* cbor, uint8_t* buffer, size_t bufferSize,
+ const uint8_t* hmacKey, size_t hmacKeySize) {
+ eicMemSet(cbor, '\0', sizeof(EicCbor));
+ cbor->size = 0;
+ cbor->bufferSize = bufferSize;
+ cbor->buffer = buffer;
+ cbor->digestType = EIC_CBOR_DIGEST_TYPE_HMAC_SHA256;
+ eicOpsHmacSha256Init(&cbor->digester.hmacSha256, hmacKey, hmacKeySize);
+}
+
+void eicCborEnableSecondaryDigesterSha256(EicCbor* cbor, EicSha256Ctx* sha256) {
+ cbor->secondaryDigesterSha256 = sha256;
+}
+
+void eicCborFinal(EicCbor* cbor, uint8_t digest[EIC_SHA256_DIGEST_SIZE]) {
+ switch (cbor->digestType) {
+ case EIC_CBOR_DIGEST_TYPE_SHA256:
+ eicOpsSha256Final(&cbor->digester.sha256, digest);
+ break;
+ case EIC_CBOR_DIGEST_TYPE_HMAC_SHA256:
+ eicOpsHmacSha256Final(&cbor->digester.hmacSha256, digest);
+ break;
+ }
+}
+
+void eicCborAppend(EicCbor* cbor, const uint8_t* data, size_t size) {
+ switch (cbor->digestType) {
+ case EIC_CBOR_DIGEST_TYPE_SHA256:
+ eicOpsSha256Update(&cbor->digester.sha256, data, size);
+ break;
+ case EIC_CBOR_DIGEST_TYPE_HMAC_SHA256:
+ eicOpsHmacSha256Update(&cbor->digester.hmacSha256, data, size);
+ break;
+ }
+ if (cbor->secondaryDigesterSha256 != NULL) {
+ eicOpsSha256Update(cbor->secondaryDigesterSha256, data, size);
+ }
+
+ if (cbor->size >= cbor->bufferSize) {
+ cbor->size += size;
+ return;
+ }
+
+ size_t numBytesLeft = cbor->bufferSize - cbor->size;
+ size_t numBytesToCopy = size;
+ if (numBytesToCopy > numBytesLeft) {
+ numBytesToCopy = numBytesLeft;
+ }
+ eicMemCpy(cbor->buffer + cbor->size, data, numBytesToCopy);
+
+ cbor->size += size;
+}
+
+size_t eicCborAdditionalLengthBytesFor(size_t size) {
+ if (size < 24) {
+ return 0;
+ } else if (size <= 0xff) {
+ return 1;
+ } else if (size <= 0xffff) {
+ return 2;
+ } else if (size <= 0xffffffff) {
+ return 4;
+ }
+ return 8;
+}
+
+void eicCborBegin(EicCbor* cbor, int majorType, uint64_t size) {
+ uint8_t data[9];
+
+ if (size < 24) {
+ data[0] = (majorType << 5) | size;
+ eicCborAppend(cbor, data, 1);
+ } else if (size <= 0xff) {
+ data[0] = (majorType << 5) | 24;
+ data[1] = size;
+ eicCborAppend(cbor, data, 2);
+ } else if (size <= 0xffff) {
+ data[0] = (majorType << 5) | 25;
+ data[1] = size >> 8;
+ data[2] = size & 0xff;
+ eicCborAppend(cbor, data, 3);
+ } else if (size <= 0xffffffff) {
+ data[0] = (majorType << 5) | 26;
+ data[1] = (size >> 24) & 0xff;
+ data[2] = (size >> 16) & 0xff;
+ data[3] = (size >> 8) & 0xff;
+ data[4] = size & 0xff;
+ eicCborAppend(cbor, data, 5);
+ } else {
+ data[0] = (majorType << 5) | 27;
+ data[1] = (((uint64_t)size) >> 56) & 0xff;
+ data[2] = (((uint64_t)size) >> 48) & 0xff;
+ data[3] = (((uint64_t)size) >> 40) & 0xff;
+ data[4] = (((uint64_t)size) >> 32) & 0xff;
+ data[5] = (((uint64_t)size) >> 24) & 0xff;
+ data[6] = (((uint64_t)size) >> 16) & 0xff;
+ data[7] = (((uint64_t)size) >> 8) & 0xff;
+ data[8] = ((uint64_t)size) & 0xff;
+ eicCborAppend(cbor, data, 9);
+ }
+}
+
+void eicCborAppendByteString(EicCbor* cbor, const uint8_t* data,
+ size_t dataSize) {
+ eicCborBegin(cbor, EIC_CBOR_MAJOR_TYPE_BYTE_STRING, dataSize);
+ eicCborAppend(cbor, data, dataSize);
+}
+
+void eicCborAppendString(EicCbor* cbor, const char* str, size_t strLength) {
+ eicCborBegin(cbor, EIC_CBOR_MAJOR_TYPE_STRING, strLength);
+ eicCborAppend(cbor, (const uint8_t*)str, strLength);
+}
+
+void eicCborAppendStringZ(EicCbor* cbor, const char* str) {
+ eicCborAppendString(cbor, str, eicStrLen(str));
+}
+
+void eicCborAppendSimple(EicCbor* cbor, uint8_t simpleValue) {
+ eicCborBegin(cbor, EIC_CBOR_MAJOR_TYPE_SIMPLE, simpleValue);
+}
+
+void eicCborAppendBool(EicCbor* cbor, bool value) {
+ uint8_t simpleValue =
+ value ? EIC_CBOR_SIMPLE_VALUE_TRUE : EIC_CBOR_SIMPLE_VALUE_FALSE;
+ eicCborAppendSimple(cbor, simpleValue);
+}
+
+void eicCborAppendSemantic(EicCbor* cbor, uint64_t value) {
+ size_t encoded = value;
+ eicCborBegin(cbor, EIC_CBOR_MAJOR_TYPE_SEMANTIC, encoded);
+}
+
+void eicCborAppendUnsigned(EicCbor* cbor, uint64_t value) {
+ uint64_t encoded = value;
+ eicCborBegin(cbor, EIC_CBOR_MAJOR_TYPE_UNSIGNED, encoded);
+}
+
+void eicCborAppendNumber(EicCbor* cbor, int64_t value) {
+ if (value < 0) {
+ uint64_t encoded = -1 - value;
+ eicCborBegin(cbor, EIC_CBOR_MAJOR_TYPE_NEGATIVE, encoded);
+ } else {
+ eicCborAppendUnsigned(cbor, value);
+ }
+}
+
+void eicCborAppendArray(EicCbor* cbor, size_t numElements) {
+ eicCborBegin(cbor, EIC_CBOR_MAJOR_TYPE_ARRAY, numElements);
+}
+
+void eicCborAppendMap(EicCbor* cbor, size_t numPairs) {
+ eicCborBegin(cbor, EIC_CBOR_MAJOR_TYPE_MAP, numPairs);
+}
+
+bool eicCborCalcAccessControl(EicCbor* cborBuilder, int id,
+ const uint8_t* readerCertificate,
+ size_t readerCertificateSize,
+ bool userAuthenticationRequired,
+ uint64_t timeoutMillis, uint64_t secureUserId) {
+ size_t numPairs = 1;
+ if (readerCertificateSize > 0) {
+ numPairs += 1;
+ }
+ if (userAuthenticationRequired) {
+ numPairs += 2;
+ if (secureUserId > 0) {
+ numPairs += 1;
+ }
+ }
+ eicCborAppendMap(cborBuilder, numPairs);
+ eicCborAppendStringZ(cborBuilder, "id");
+ eicCborAppendUnsigned(cborBuilder, id);
+ if (readerCertificateSize > 0) {
+ eicCborAppendStringZ(cborBuilder, "readerCertificate");
+ eicCborAppendByteString(cborBuilder, readerCertificate,
+ readerCertificateSize);
+ }
+ if (userAuthenticationRequired) {
+ eicCborAppendStringZ(cborBuilder, "userAuthenticationRequired");
+ eicCborAppendBool(cborBuilder, userAuthenticationRequired);
+ eicCborAppendStringZ(cborBuilder, "timeoutMillis");
+ eicCborAppendUnsigned(cborBuilder, timeoutMillis);
+ if (secureUserId > 0) {
+ eicCborAppendStringZ(cborBuilder, "secureUserId");
+ eicCborAppendUnsigned(cborBuilder, secureUserId);
+ }
+ }
+
+ if (cborBuilder->size > cborBuilder->bufferSize) {
+ eicDebug("Buffer for ACP CBOR is too small (%zd) - need %zd bytes",
+ cborBuilder->bufferSize, cborBuilder->size);
+ return false;
+ }
+
+ return true;
+}
+
+bool eicCborCalcEntryAdditionalData(
+ const uint8_t* accessControlProfileIds, size_t numAccessControlProfileIds,
+ const char* nameSpace, size_t nameSpaceLength, const char* name,
+ size_t nameLength, uint8_t* cborBuffer, size_t cborBufferSize,
+ size_t* outAdditionalDataCborSize,
+ uint8_t additionalDataSha256[EIC_SHA256_DIGEST_SIZE]) {
+ EicCbor cborBuilder;
+
+ eicCborInit(&cborBuilder, cborBuffer, cborBufferSize);
+ eicCborAppendMap(&cborBuilder, 3);
+ eicCborAppendStringZ(&cborBuilder, "Namespace");
+ eicCborAppendString(&cborBuilder, nameSpace, nameSpaceLength);
+ eicCborAppendStringZ(&cborBuilder, "Name");
+ eicCborAppendString(&cborBuilder, name, nameLength);
+ eicCborAppendStringZ(&cborBuilder, "AccessControlProfileIds");
+ eicCborAppendArray(&cborBuilder, numAccessControlProfileIds);
+ for (size_t n = 0; n < numAccessControlProfileIds; n++) {
+ eicCborAppendNumber(&cborBuilder, accessControlProfileIds[n]);
+ }
+ if (cborBuilder.size > cborBufferSize) {
+ eicDebug(
+ "Not enough space for additionalData - buffer is only %zd bytes, "
+ "content is %zd",
+ cborBufferSize, cborBuilder.size);
+ return false;
+ }
+ if (outAdditionalDataCborSize != NULL) {
+ *outAdditionalDataCborSize = cborBuilder.size;
+ }
+ eicCborFinal(&cborBuilder, additionalDataSha256);
+ return true;
+}
diff --git a/guest/hals/identity/libeic/EicCbor.h b/guest/hals/identity/libeic/EicCbor.h
new file mode 100644
index 0000000..384766f
--- /dev/null
+++ b/guest/hals/identity/libeic/EicCbor.h
@@ -0,0 +1,160 @@
+/*
+ * Copyright 2020, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef ANDROID_HARDWARE_IDENTITY_EIC_CBOR_H
+#define ANDROID_HARDWARE_IDENTITY_EIC_CBOR_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include "EicOps.h"
+
+typedef enum {
+ EIC_CBOR_DIGEST_TYPE_SHA256,
+ EIC_CBOR_DIGEST_TYPE_HMAC_SHA256,
+} EicCborDigestType;
+
+/* EicCbor is a utility class to build CBOR data structures and calculate
+ * digests on the fly.
+ */
+typedef struct {
+ // Contains the size of the built CBOR, even if it exceeds bufferSize (will
+ // never write to buffer beyond bufferSize though)
+ size_t size;
+
+ // The size of the buffer. Is zero if no data is recorded in which case
+ // only digesting is performed.
+ size_t bufferSize;
+
+ // Whether we're producing a SHA-256 or HMAC-SHA256 digest.
+ EicCborDigestType digestType;
+
+ // The SHA-256 digester object.
+ union {
+ EicSha256Ctx sha256;
+ EicHmacSha256Ctx hmacSha256;
+ } digester;
+
+ // The secondary digester, may be unset.
+ EicSha256Ctx* secondaryDigesterSha256;
+
+ // The buffer used for building up CBOR or NULL if bufferSize is 0.
+ uint8_t* buffer;
+} EicCbor;
+
+/* Initializes an EicCbor.
+ *
+ * The given buffer will be used, up to bufferSize.
+ *
+ * If bufferSize is 0, buffer may be NULL.
+ */
+void eicCborInit(EicCbor* cbor, uint8_t* buffer, size_t bufferSize);
+
+/* Like eicCborInit() but uses HMAC-SHA256 instead of SHA-256.
+ */
+void eicCborInitHmacSha256(EicCbor* cbor, uint8_t* buffer, size_t bufferSize,
+ const uint8_t* hmacKey, size_t hmacKeySize);
+
+/* Enables a secondary digester.
+ *
+ * May be enabled midway through processing, this can be used to e.g. calculate
+ * a digest of Sig_structure (for COSE_Sign1) and a separate digest of its
+ * payload.
+ */
+void eicCborEnableSecondaryDigesterSha256(EicCbor* cbor, EicSha256Ctx* sha256);
+
+/* Finishes building CBOR and returns the digest. */
+void eicCborFinal(EicCbor* cbor, uint8_t digest[EIC_SHA256_DIGEST_SIZE]);
+
+/* Appends CBOR data to the EicCbor. */
+void eicCborAppend(EicCbor* cbor, const uint8_t* data, size_t size);
+
+#define EIC_CBOR_MAJOR_TYPE_UNSIGNED 0
+#define EIC_CBOR_MAJOR_TYPE_NEGATIVE 1
+#define EIC_CBOR_MAJOR_TYPE_BYTE_STRING 2
+#define EIC_CBOR_MAJOR_TYPE_STRING 3
+#define EIC_CBOR_MAJOR_TYPE_ARRAY 4
+#define EIC_CBOR_MAJOR_TYPE_MAP 5
+#define EIC_CBOR_MAJOR_TYPE_SEMANTIC 6
+#define EIC_CBOR_MAJOR_TYPE_SIMPLE 7
+
+#define EIC_CBOR_SIMPLE_VALUE_FALSE 20
+#define EIC_CBOR_SIMPLE_VALUE_TRUE 21
+
+#define EIC_CBOR_SEMANTIC_TAG_ENCODED_CBOR 24
+
+/* Begins a new CBOR value. */
+void eicCborBegin(EicCbor* cbor, int majorType, uint64_t size);
+
+/* Appends a bytestring. */
+void eicCborAppendByteString(EicCbor* cbor, const uint8_t* data,
+ size_t dataSize);
+
+/* Appends a UTF-8 string. */
+void eicCborAppendString(EicCbor* cbor, const char* str, size_t strLength);
+
+/* Appends a NUL-terminated UTF-8 string. */
+void eicCborAppendStringZ(EicCbor* cbor, const char* str);
+
+/* Appends a simple value. */
+void eicCborAppendSimple(EicCbor* cbor, uint8_t simpleValue);
+
+/* Appends a boolean. */
+void eicCborAppendBool(EicCbor* cbor, bool value);
+
+/* Appends a semantic */
+void eicCborAppendSemantic(EicCbor* cbor, uint64_t value);
+
+/* Appends an unsigned number. */
+void eicCborAppendUnsigned(EicCbor* cbor, uint64_t value);
+
+/* Appends a number. */
+void eicCborAppendNumber(EicCbor* cbor, int64_t value);
+
+/* Starts appending an array.
+ *
+ * After this numElements CBOR elements must follow.
+ */
+void eicCborAppendArray(EicCbor* cbor, size_t numElements);
+
+/* Starts appending a map.
+ *
+ * After this numPairs pairs of CBOR elements must follow.
+ */
+void eicCborAppendMap(EicCbor* cbor, size_t numPairs);
+
+/* Calculates how many bytes are needed to store a size. */
+size_t eicCborAdditionalLengthBytesFor(size_t size);
+
+bool eicCborCalcAccessControl(EicCbor* cborBuilder, int id,
+ const uint8_t* readerCertificate,
+ size_t readerCertificateSize,
+ bool userAuthenticationRequired,
+ uint64_t timeoutMillis, uint64_t secureUserId);
+
+bool eicCborCalcEntryAdditionalData(
+ const uint8_t* accessControlProfileIds, size_t numAccessControlProfileIds,
+ const char* nameSpace, size_t nameSpaceLength, const char* name,
+ size_t nameLength, uint8_t* cborBuffer, size_t cborBufferSize,
+ size_t* outAdditionalDataCborSize,
+ uint8_t additionalDataSha256[EIC_SHA256_DIGEST_SIZE]);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif // ANDROID_HARDWARE_IDENTITY_EIC_CBOR_H
diff --git a/guest/hals/identity/libeic/EicCommon.h b/guest/hals/identity/libeic/EicCommon.h
new file mode 100644
index 0000000..1fab26a
--- /dev/null
+++ b/guest/hals/identity/libeic/EicCommon.h
@@ -0,0 +1,40 @@
+/*
+ * Copyright 2020, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef ANDROID_HARDWARE_IDENTITY_EIC_COMMON_H
+#define ANDROID_HARDWARE_IDENTITY_EIC_COMMON_H
+
+// Feature version 202009:
+//
+// CredentialKeys = [
+// bstr, ; storageKey, a 128-bit AES key
+// bstr, ; credentialPrivKey, the private key for credentialKey
+// ]
+//
+// Feature version 202101:
+//
+// CredentialKeys = [
+// bstr, ; storageKey, a 128-bit AES key
+// bstr, ; credentialPrivKey, the private key for credentialKey
+// bstr ; proofOfProvisioning SHA-256
+// ]
+//
+// where storageKey is 16 bytes, credentialPrivateKey is 32 bytes, and
+// proofOfProvisioning SHA-256 is 32 bytes.
+#define EIC_CREDENTIAL_KEYS_CBOR_SIZE_FEATURE_VERSION_202009 52
+#define EIC_CREDENTIAL_KEYS_CBOR_SIZE_FEATURE_VERSION_202101 86
+
+#endif // ANDROID_HARDWARE_IDENTITY_EIC_COMMON_H
diff --git a/guest/hals/identity/libeic/EicOps.h b/guest/hals/identity/libeic/EicOps.h
new file mode 100644
index 0000000..849d6fc
--- /dev/null
+++ b/guest/hals/identity/libeic/EicOps.h
@@ -0,0 +1,316 @@
+/*
+ * Copyright 2020, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef ANDROID_HARDWARE_IDENTITY_EIC_OPS_H
+#define ANDROID_HARDWARE_IDENTITY_EIC_OPS_H
+
+#include <stdarg.h>
+#include <stdbool.h>
+#include <stddef.h>
+#include <stdlib.h>
+
+// Uncomment or define if debug messages are needed.
+//
+//#define EIC_DEBUG
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+// The following defines must be set to something appropriate
+//
+// EIC_SHA256_CONTEXT_SIZE - the size of EicSha256Ctx
+// EIC_HMAC_SHA256_CONTEXT_SIZE - the size of EicHmacSha256Ctx
+//
+// For example, if EicSha256Ctx is implemented using BoringSSL this would be
+// defined as sizeof(SHA256_CTX).
+//
+// We expect the implementation to provide a header file with the name
+// EicOpsImpl.h to do all this.
+//
+#include "EicOpsImpl.h"
+
+#define EIC_SHA256_DIGEST_SIZE 32
+
+// The size of a P-256 private key.
+//
+#define EIC_P256_PRIV_KEY_SIZE 32
+
+// The size of a P-256 public key in uncompressed form.
+//
+// The public key is stored in uncompressed form, first the X coordinate, then
+// the Y coordinate.
+//
+#define EIC_P256_PUB_KEY_SIZE 64
+
+// Size of one of the coordinates in a curve-point.
+//
+#define EIC_P256_COORDINATE_SIZE 32
+
+// The size of an ECSDA signature using P-256.
+//
+// The R and S values are stored here, first R then S.
+//
+#define EIC_ECDSA_P256_SIGNATURE_SIZE 64
+
+#define EIC_AES_128_KEY_SIZE 16
+
+// The following are definitions of implementation functions the
+// underlying platform must provide.
+//
+
+struct EicSha256Ctx {
+ uint8_t reserved[EIC_SHA256_CONTEXT_SIZE];
+};
+typedef struct EicSha256Ctx EicSha256Ctx;
+
+struct EicHmacSha256Ctx {
+ uint8_t reserved[EIC_HMAC_SHA256_CONTEXT_SIZE];
+};
+typedef struct EicHmacSha256Ctx EicHmacSha256Ctx;
+
+#ifdef EIC_DEBUG
+// Debug macro. Don't include a new-line in message.
+//
+#define eicDebug(...) \
+ do { \
+ eicPrint("%s:%d: ", __FILE__, __LINE__); \
+ eicPrint(__VA_ARGS__); \
+ eicPrint("\n"); \
+ } while (0)
+#else
+#define eicDebug(...) \
+ do { \
+ } while (0)
+#endif
+
+// Prints message which should include new-line character. Can be no-op.
+//
+// Don't use this from code, use eicDebug() instead.
+//
+#ifdef EIC_DEBUG
+void eicPrint(const char* format, ...);
+#else
+inline void eicPrint(const char*, ...) {}
+#endif
+
+// Dumps data as pretty-printed hex. Can be no-op.
+//
+#ifdef EIC_DEBUG
+void eicHexdump(const char* message, const uint8_t* data, size_t dataSize);
+#else
+inline void eicHexdump(const char*, const uint8_t*, size_t) {}
+#endif
+
+// Pretty-prints encoded CBOR. Can be no-op.
+//
+// If a byte-string is larger than |maxBStrSize| its contents will not be
+// printed, instead the value of the form "<bstr size=1099016
+// sha1=ef549cca331f73dfae2090e6a37c04c23f84b07b>" will be printed. Pass zero
+// for |maxBStrSize| to disable this.
+//
+#ifdef EIC_DEBUG
+void eicCborPrettyPrint(const uint8_t* cborData, size_t cborDataSize,
+ size_t maxBStrSize);
+#else
+inline void eicCborPrettyPrint(const uint8_t*, size_t, size_t) {}
+#endif
+
+// Memory setting, see memset(3).
+void* eicMemSet(void* s, int c, size_t n);
+
+// Memory copying, see memcpy(3).
+void* eicMemCpy(void* dest, const void* src, size_t n);
+
+// String length, see strlen(3).
+size_t eicStrLen(const char* s);
+
+// Memory compare, see CRYPTO_memcmp(3SSL)
+//
+// It takes an amount of time dependent on len, but independent of the contents
+// of the memory regions pointed to by s1 and s2.
+//
+int eicCryptoMemCmp(const void* s1, const void* s2, size_t n);
+
+// Random number generation.
+bool eicOpsRandom(uint8_t* buf, size_t numBytes);
+
+// If |testCredential| is true, returns the 128-bit AES Hardware-Bound Key (16
+// bytes).
+//
+// Otherwise returns all zeroes (16 bytes).
+//
+const uint8_t* eicOpsGetHardwareBoundKey(bool testCredential);
+
+// Encrypts |data| with |key| and |additionalAuthenticatedData| using |nonce|,
+// returns the resulting (nonce || ciphertext || tag) in |encryptedData| which
+// must be of size |dataSize| + 28.
+bool eicOpsEncryptAes128Gcm(
+ const uint8_t* key, // Must be 16 bytes
+ const uint8_t* nonce, // Must be 12 bytes
+ const uint8_t* data, // May be NULL if size is 0
+ size_t dataSize,
+ const uint8_t* additionalAuthenticationData, // May be NULL if size is 0
+ size_t additionalAuthenticationDataSize, uint8_t* encryptedData);
+
+// Decrypts |encryptedData| using |key| and |additionalAuthenticatedData|,
+// returns resulting plaintext in |data| must be of size |encryptedDataSize|
+// - 28.
+//
+// The format of |encryptedData| must be as specified in the
+// encryptAes128Gcm() function.
+bool eicOpsDecryptAes128Gcm(const uint8_t* key, // Must be 16 bytes
+ const uint8_t* encryptedData,
+ size_t encryptedDataSize,
+ const uint8_t* additionalAuthenticationData,
+ size_t additionalAuthenticationDataSize,
+ uint8_t* data);
+
+// Creates an EC key using the P-256 curve. The private key is written to
+// |privateKey|. The public key is written to |publicKey|.
+//
+bool eicOpsCreateEcKey(uint8_t privateKey[EIC_P256_PRIV_KEY_SIZE],
+ uint8_t publicKey[EIC_P256_PUB_KEY_SIZE]);
+
+// Generates CredentialKey plus an attestation certificate.
+//
+// The attestation certificate will be signed by the attestation keys the secure
+// area has been provisioned with. The given |challenge| and |applicationId|
+// will be used as will |testCredential|.
+//
+// The generated certificate will be in X.509 format and returned in |cert|
+// and |certSize| must be set to the size of this array and this function will
+// set it to the size of the certification chain on successfully return.
+//
+// This may return either a single certificate or an entire certificate
+// chain. If it returns only a single certificate, the implementation of
+// SecureHardwareProvisioningProxy::createCredentialKey() should amend the
+// remainder of the certificate chain on the HAL side.
+//
+bool eicOpsCreateCredentialKey(uint8_t privateKey[EIC_P256_PRIV_KEY_SIZE],
+ const uint8_t* challenge, size_t challengeSize,
+ const uint8_t* applicationId,
+ size_t applicationIdSize, bool testCredential,
+ uint8_t* cert,
+ size_t* certSize); // inout
+
+// Generate an X.509 certificate for the key identified by |publicKey| which
+// must be of the form returned by eicOpsCreateEcKey().
+//
+// If proofOfBinding is not NULL, it will be included as an OCTET_STRING
+// X.509 extension at OID 1.3.6.1.4.1.11129.2.1.26.
+//
+// The certificate will be signed by the key identified by |signingKey| which
+// must be of the form returned by eicOpsCreateEcKey().
+//
+bool eicOpsSignEcKey(const uint8_t publicKey[EIC_P256_PUB_KEY_SIZE],
+ const uint8_t signingKey[EIC_P256_PRIV_KEY_SIZE],
+ unsigned int serial, const char* issuerName,
+ const char* subjectName, time_t validityNotBefore,
+ time_t validityNotAfter, const uint8_t* proofOfBinding,
+ size_t proofOfBindingSize, uint8_t* cert,
+ size_t* certSize); // inout
+
+// Uses |privateKey| to create an ECDSA signature of some data (the SHA-256 must
+// be given by |digestOfData|). Returns the signature in |signature|.
+//
+bool eicOpsEcDsa(const uint8_t privateKey[EIC_P256_PRIV_KEY_SIZE],
+ const uint8_t digestOfData[EIC_SHA256_DIGEST_SIZE],
+ uint8_t signature[EIC_ECDSA_P256_SIGNATURE_SIZE]);
+
+// Performs Elliptic Curve Diffie-Helman.
+//
+bool eicOpsEcdh(const uint8_t publicKey[EIC_P256_PUB_KEY_SIZE],
+ const uint8_t privateKey[EIC_P256_PRIV_KEY_SIZE],
+ uint8_t sharedSecret[EIC_P256_COORDINATE_SIZE]);
+
+// Performs HKDF.
+//
+bool eicOpsHkdf(const uint8_t* sharedSecret, size_t sharedSecretSize,
+ const uint8_t* salt, size_t saltSize, const uint8_t* info,
+ size_t infoSize, uint8_t* output, size_t outputSize);
+
+// SHA-256 functions.
+void eicOpsSha256Init(EicSha256Ctx* ctx);
+void eicOpsSha256Update(EicSha256Ctx* ctx, const uint8_t* data, size_t len);
+void eicOpsSha256Final(EicSha256Ctx* ctx,
+ uint8_t digest[EIC_SHA256_DIGEST_SIZE]);
+
+// HMAC SHA-256 functions.
+void eicOpsHmacSha256Init(EicHmacSha256Ctx* ctx, const uint8_t* key,
+ size_t keySize);
+void eicOpsHmacSha256Update(EicHmacSha256Ctx* ctx, const uint8_t* data,
+ size_t len);
+void eicOpsHmacSha256Final(EicHmacSha256Ctx* ctx,
+ uint8_t digest[EIC_SHA256_DIGEST_SIZE]);
+
+// Extracts the public key in the given X.509 certificate.
+//
+// If the key is not an EC key, this function fails.
+//
+// Otherwise the public key is stored in uncompressed form in |publicKey| which
+// size should be set in |publicKeySize|. On successful return |publicKeySize|
+// is set to the length of the key. If there is not enough space, the function
+// fails.
+//
+// (The public key returned is not necessarily a P-256 key, even if it is note
+// that its size is not EIC_P256_PUBLIC_KEY_SIZE because of the leading 0x04.)
+//
+bool eicOpsX509GetPublicKey(const uint8_t* x509Cert, size_t x509CertSize,
+ uint8_t* publicKey, size_t* publicKeySize);
+
+// Checks that the X.509 certificate given by |x509Cert| is signed by the public
+// key given by |publicKey| which must be an EC key in uncompressed form (e.g.
+// same formatt as returned by eicOpsX509GetPublicKey()).
+//
+bool eicOpsX509CertSignedByPublicKey(const uint8_t* x509Cert,
+ size_t x509CertSize,
+ const uint8_t* publicKey,
+ size_t publicKeySize);
+
+// Checks that |signature| is a signature of some data (given by |digest|),
+// signed by the public key given by |publicKey|.
+//
+// The key must be an EC key in uncompressed form (e.g. same format as returned
+// by eicOpsX509GetPublicKey()).
+//
+// The format of the signature is the same encoding as the 'signature' field of
+// COSE_Sign1 - that is, it's the R and S integers both with the same length as
+// the key-size.
+//
+// The size of digest must match the size of the key.
+//
+bool eicOpsEcDsaVerifyWithPublicKey(const uint8_t* digest, size_t digestSize,
+ const uint8_t* signature,
+ size_t signatureSize,
+ const uint8_t* publicKey,
+ size_t publicKeySize);
+
+// Validates that the passed in data constitutes a valid auth- and verification
+// tokens.
+//
+bool eicOpsValidateAuthToken(
+ uint64_t challenge, uint64_t secureUserId, uint64_t authenticatorId,
+ int hardwareAuthenticatorType, uint64_t timeStamp, const uint8_t* mac,
+ size_t macSize, uint64_t verificationTokenChallenge,
+ uint64_t verificationTokenTimeStamp, int verificationTokenSecurityLevel,
+ const uint8_t* verificationTokenMac, size_t verificationTokenMacSize);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif // ANDROID_HARDWARE_IDENTITY_EIC_OPS_H
diff --git a/guest/hals/identity/libeic/EicOpsImpl.cc b/guest/hals/identity/libeic/EicOpsImpl.cc
new file mode 100644
index 0000000..0921c72
--- /dev/null
+++ b/guest/hals/identity/libeic/EicOpsImpl.cc
@@ -0,0 +1,546 @@
+/*
+ * Copyright 2020, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#define LOG_TAG "EicOpsImpl"
+
+#include <optional>
+#include <tuple>
+#include <vector>
+
+#include <android-base/logging.h>
+#include <android-base/stringprintf.h>
+#include <string.h>
+
+#include <android/hardware/identity/support/IdentityCredentialSupport.h>
+
+#include <openssl/sha.h>
+
+#include <openssl/aes.h>
+#include <openssl/bn.h>
+#include <openssl/crypto.h>
+#include <openssl/ec.h>
+#include <openssl/err.h>
+#include <openssl/evp.h>
+#include <openssl/hkdf.h>
+#include <openssl/hmac.h>
+#include <openssl/objects.h>
+#include <openssl/pem.h>
+#include <openssl/pkcs12.h>
+#include <openssl/rand.h>
+#include <openssl/x509.h>
+#include <openssl/x509_vfy.h>
+
+#include "EicOps.h"
+
+using ::std::map;
+using ::std::optional;
+using ::std::string;
+using ::std::tuple;
+using ::std::vector;
+
+void* eicMemSet(void* s, int c, size_t n) { return memset(s, c, n); }
+
+void* eicMemCpy(void* dest, const void* src, size_t n) {
+ return memcpy(dest, src, n);
+}
+
+size_t eicStrLen(const char* s) { return strlen(s); }
+
+int eicCryptoMemCmp(const void* s1, const void* s2, size_t n) {
+ return CRYPTO_memcmp(s1, s2, n);
+}
+
+void eicOpsHmacSha256Init(EicHmacSha256Ctx* ctx, const uint8_t* key,
+ size_t keySize) {
+ HMAC_CTX* realCtx = (HMAC_CTX*)ctx;
+ HMAC_CTX_init(realCtx);
+ if (HMAC_Init_ex(realCtx, key, keySize, EVP_sha256(), nullptr /* impl */) !=
+ 1) {
+ LOG(ERROR) << "Error initializing HMAC_CTX";
+ }
+}
+
+void eicOpsHmacSha256Update(EicHmacSha256Ctx* ctx, const uint8_t* data,
+ size_t len) {
+ HMAC_CTX* realCtx = (HMAC_CTX*)ctx;
+ if (HMAC_Update(realCtx, data, len) != 1) {
+ LOG(ERROR) << "Error updating HMAC_CTX";
+ }
+}
+
+void eicOpsHmacSha256Final(EicHmacSha256Ctx* ctx,
+ uint8_t digest[EIC_SHA256_DIGEST_SIZE]) {
+ HMAC_CTX* realCtx = (HMAC_CTX*)ctx;
+ unsigned int size = 0;
+ if (HMAC_Final(realCtx, digest, &size) != 1) {
+ LOG(ERROR) << "Error finalizing HMAC_CTX";
+ }
+ if (size != EIC_SHA256_DIGEST_SIZE) {
+ LOG(ERROR) << "Expected 32 bytes from HMAC_Final, got " << size;
+ }
+}
+
+void eicOpsSha256Init(EicSha256Ctx* ctx) {
+ SHA256_CTX* realCtx = (SHA256_CTX*)ctx;
+ SHA256_Init(realCtx);
+}
+
+void eicOpsSha256Update(EicSha256Ctx* ctx, const uint8_t* data, size_t len) {
+ SHA256_CTX* realCtx = (SHA256_CTX*)ctx;
+ SHA256_Update(realCtx, data, len);
+}
+
+void eicOpsSha256Final(EicSha256Ctx* ctx,
+ uint8_t digest[EIC_SHA256_DIGEST_SIZE]) {
+ SHA256_CTX* realCtx = (SHA256_CTX*)ctx;
+ SHA256_Final(digest, realCtx);
+}
+
+bool eicOpsRandom(uint8_t* buf, size_t numBytes) {
+ optional<vector<uint8_t>> bytes =
+ ::android::hardware::identity::support::getRandom(numBytes);
+ if (!bytes.has_value()) {
+ return false;
+ }
+ memcpy(buf, bytes.value().data(), numBytes);
+ return true;
+}
+
+bool eicOpsEncryptAes128Gcm(
+ const uint8_t* key, // Must be 16 bytes
+ const uint8_t* nonce, // Must be 12 bytes
+ const uint8_t* data, // May be NULL if size is 0
+ size_t dataSize,
+ const uint8_t* additionalAuthenticationData, // May be NULL if size is 0
+ size_t additionalAuthenticationDataSize, uint8_t* encryptedData) {
+ vector<uint8_t> cppKey;
+ cppKey.resize(16);
+ memcpy(cppKey.data(), key, 16);
+
+ vector<uint8_t> cppData;
+ cppData.resize(dataSize);
+ if (dataSize > 0) {
+ memcpy(cppData.data(), data, dataSize);
+ }
+
+ vector<uint8_t> cppAAD;
+ cppAAD.resize(additionalAuthenticationDataSize);
+ if (additionalAuthenticationDataSize > 0) {
+ memcpy(cppAAD.data(), additionalAuthenticationData,
+ additionalAuthenticationDataSize);
+ }
+
+ vector<uint8_t> cppNonce;
+ cppNonce.resize(12);
+ memcpy(cppNonce.data(), nonce, 12);
+
+ optional<vector<uint8_t>> cppEncryptedData =
+ android::hardware::identity::support::encryptAes128Gcm(cppKey, cppNonce,
+ cppData, cppAAD);
+ if (!cppEncryptedData.has_value()) {
+ return false;
+ }
+
+ memcpy(encryptedData, cppEncryptedData.value().data(),
+ cppEncryptedData.value().size());
+ return true;
+}
+
+// Decrypts |encryptedData| using |key| and |additionalAuthenticatedData|,
+// returns resulting plaintext in |data| must be of size |encryptedDataSize|
+// - 28.
+//
+// The format of |encryptedData| must be as specified in the
+// encryptAes128Gcm() function.
+bool eicOpsDecryptAes128Gcm(const uint8_t* key, // Must be 16 bytes
+ const uint8_t* encryptedData,
+ size_t encryptedDataSize,
+ const uint8_t* additionalAuthenticationData,
+ size_t additionalAuthenticationDataSize,
+ uint8_t* data) {
+ vector<uint8_t> keyVec;
+ keyVec.resize(16);
+ memcpy(keyVec.data(), key, 16);
+
+ vector<uint8_t> encryptedDataVec;
+ encryptedDataVec.resize(encryptedDataSize);
+ if (encryptedDataSize > 0) {
+ memcpy(encryptedDataVec.data(), encryptedData, encryptedDataSize);
+ }
+
+ vector<uint8_t> aadVec;
+ aadVec.resize(additionalAuthenticationDataSize);
+ if (additionalAuthenticationDataSize > 0) {
+ memcpy(aadVec.data(), additionalAuthenticationData,
+ additionalAuthenticationDataSize);
+ }
+
+ optional<vector<uint8_t>> decryptedDataVec =
+ android::hardware::identity::support::decryptAes128Gcm(
+ keyVec, encryptedDataVec, aadVec);
+ if (!decryptedDataVec.has_value()) {
+ eicDebug("Error decrypting data");
+ return false;
+ }
+ if (decryptedDataVec.value().size() != encryptedDataSize - 28) {
+ eicDebug("Decrypted data is size %zd, expected %zd",
+ decryptedDataVec.value().size(), encryptedDataSize - 28);
+ return false;
+ }
+
+ if (decryptedDataVec.value().size() > 0) {
+ memcpy(data, decryptedDataVec.value().data(),
+ decryptedDataVec.value().size());
+ }
+ return true;
+}
+
+bool eicOpsCreateEcKey(uint8_t privateKey[EIC_P256_PRIV_KEY_SIZE],
+ uint8_t publicKey[EIC_P256_PUB_KEY_SIZE]) {
+ optional<vector<uint8_t>> keyPair =
+ android::hardware::identity::support::createEcKeyPair();
+ if (!keyPair) {
+ eicDebug("Error creating EC keypair");
+ return false;
+ }
+ optional<vector<uint8_t>> privKey =
+ android::hardware::identity::support::ecKeyPairGetPrivateKey(
+ keyPair.value());
+ if (!privKey) {
+ eicDebug("Error extracting private key");
+ return false;
+ }
+ if (privKey.value().size() != EIC_P256_PRIV_KEY_SIZE) {
+ eicDebug("Private key is %zd bytes, expected %zd", privKey.value().size(),
+ (size_t)EIC_P256_PRIV_KEY_SIZE);
+ return false;
+ }
+
+ optional<vector<uint8_t>> pubKey =
+ android::hardware::identity::support::ecKeyPairGetPublicKey(
+ keyPair.value());
+ if (!pubKey) {
+ eicDebug("Error extracting public key");
+ return false;
+ }
+ // ecKeyPairGetPublicKey() returns 0x04 | x | y, we don't want the leading
+ // 0x04.
+ if (pubKey.value().size() != EIC_P256_PUB_KEY_SIZE + 1) {
+ eicDebug("Public key is %zd bytes long, expected %zd",
+ pubKey.value().size(), (size_t)EIC_P256_PRIV_KEY_SIZE + 1);
+ return false;
+ }
+
+ memcpy(privateKey, privKey.value().data(), EIC_P256_PRIV_KEY_SIZE);
+ memcpy(publicKey, pubKey.value().data() + 1, EIC_P256_PUB_KEY_SIZE);
+
+ return true;
+}
+
+bool eicOpsCreateCredentialKey(uint8_t privateKey[EIC_P256_PRIV_KEY_SIZE],
+ const uint8_t* challenge, size_t challengeSize,
+ const uint8_t* applicationId,
+ size_t applicationIdSize, bool testCredential,
+ uint8_t* cert, size_t* certSize) {
+ vector<uint8_t> challengeVec(challengeSize);
+ memcpy(challengeVec.data(), challenge, challengeSize);
+
+ vector<uint8_t> applicationIdVec(applicationIdSize);
+ memcpy(applicationIdVec.data(), applicationId, applicationIdSize);
+
+ optional<std::pair<vector<uint8_t>, vector<vector<uint8_t>>>> ret =
+ android::hardware::identity::support::createEcKeyPairAndAttestation(
+ challengeVec, applicationIdVec, testCredential);
+ if (!ret) {
+ eicDebug("Error generating CredentialKey and attestation");
+ return false;
+ }
+
+ // Extract certificate chain.
+ vector<uint8_t> flatChain =
+ android::hardware::identity::support::certificateChainJoin(
+ ret.value().second);
+ if (*certSize < flatChain.size()) {
+ eicDebug("Buffer for certificate is only %zd bytes long, need %zd bytes",
+ *certSize, flatChain.size());
+ return false;
+ }
+ memcpy(cert, flatChain.data(), flatChain.size());
+ *certSize = flatChain.size();
+
+ // Extract private key.
+ optional<vector<uint8_t>> privKey =
+ android::hardware::identity::support::ecKeyPairGetPrivateKey(
+ ret.value().first);
+ if (!privKey) {
+ eicDebug("Error extracting private key");
+ return false;
+ }
+ if (privKey.value().size() != EIC_P256_PRIV_KEY_SIZE) {
+ eicDebug("Private key is %zd bytes, expected %zd", privKey.value().size(),
+ (size_t)EIC_P256_PRIV_KEY_SIZE);
+ return false;
+ }
+
+ memcpy(privateKey, privKey.value().data(), EIC_P256_PRIV_KEY_SIZE);
+
+ return true;
+}
+
+bool eicOpsSignEcKey(const uint8_t publicKey[EIC_P256_PUB_KEY_SIZE],
+ const uint8_t signingKey[EIC_P256_PRIV_KEY_SIZE],
+ unsigned int serial, const char* issuerName,
+ const char* subjectName, time_t validityNotBefore,
+ time_t validityNotAfter, const uint8_t* proofOfBinding,
+ size_t proofOfBindingSize, uint8_t* cert,
+ size_t* certSize) { // inout
+ vector<uint8_t> signingKeyVec(EIC_P256_PRIV_KEY_SIZE);
+ memcpy(signingKeyVec.data(), signingKey, EIC_P256_PRIV_KEY_SIZE);
+
+ vector<uint8_t> pubKeyVec(EIC_P256_PUB_KEY_SIZE + 1);
+ pubKeyVec[0] = 0x04;
+ memcpy(pubKeyVec.data() + 1, publicKey, EIC_P256_PUB_KEY_SIZE);
+
+ string serialDecimal = android::base::StringPrintf("%d", serial);
+
+ map<string, vector<uint8_t>> extensions;
+ if (proofOfBinding != nullptr) {
+ vector<uint8_t> proofOfBindingVec(proofOfBinding,
+ proofOfBinding + proofOfBindingSize);
+ extensions["1.3.6.1.4.1.11129.2.1.26"] = proofOfBindingVec;
+ }
+
+ optional<vector<uint8_t>> certVec =
+ android::hardware::identity::support::ecPublicKeyGenerateCertificate(
+ pubKeyVec, signingKeyVec, serialDecimal, issuerName, subjectName,
+ validityNotBefore, validityNotAfter, extensions);
+ if (!certVec) {
+ eicDebug("Error generating certificate");
+ return false;
+ }
+
+ if (*certSize < certVec.value().size()) {
+ eicDebug("Buffer for certificate is only %zd bytes long, need %zd bytes",
+ *certSize, certVec.value().size());
+ return false;
+ }
+ memcpy(cert, certVec.value().data(), certVec.value().size());
+ *certSize = certVec.value().size();
+
+ return true;
+}
+
+bool eicOpsEcDsa(const uint8_t privateKey[EIC_P256_PRIV_KEY_SIZE],
+ const uint8_t digestOfData[EIC_SHA256_DIGEST_SIZE],
+ uint8_t signature[EIC_ECDSA_P256_SIGNATURE_SIZE]) {
+ vector<uint8_t> privKeyVec(EIC_P256_PRIV_KEY_SIZE);
+ memcpy(privKeyVec.data(), privateKey, EIC_P256_PRIV_KEY_SIZE);
+
+ vector<uint8_t> digestVec(EIC_SHA256_DIGEST_SIZE);
+ memcpy(digestVec.data(), digestOfData, EIC_SHA256_DIGEST_SIZE);
+
+ optional<vector<uint8_t>> derSignature =
+ android::hardware::identity::support::signEcDsaDigest(privKeyVec,
+ digestVec);
+ if (!derSignature) {
+ eicDebug("Error signing data");
+ return false;
+ }
+
+ ECDSA_SIG* sig;
+ const unsigned char* p = derSignature.value().data();
+ sig = d2i_ECDSA_SIG(nullptr, &p, derSignature.value().size());
+ if (sig == nullptr) {
+ eicDebug("Error decoding DER signature");
+ return false;
+ }
+
+ if (BN_bn2binpad(sig->r, signature, 32) != 32) {
+ eicDebug("Error encoding r");
+ return false;
+ }
+ if (BN_bn2binpad(sig->s, signature + 32, 32) != 32) {
+ eicDebug("Error encoding s");
+ return false;
+ }
+
+ return true;
+}
+
+static const uint8_t hbkTest[16] = {0};
+static const uint8_t hbkReal[16] = {0, 1, 2, 3, 4, 5, 6, 7,
+ 8, 9, 10, 11, 12, 13, 14, 15};
+
+const uint8_t* eicOpsGetHardwareBoundKey(bool testCredential) {
+ if (testCredential) {
+ return hbkTest;
+ }
+ return hbkReal;
+}
+
+bool eicOpsValidateAuthToken(uint64_t /* challenge */,
+ uint64_t /* secureUserId */,
+ uint64_t /* authenticatorId */,
+ int /* hardwareAuthenticatorType */,
+ uint64_t /* timeStamp */, const uint8_t* /* mac */,
+ size_t /* macSize */,
+ uint64_t /* verificationTokenChallenge */,
+ uint64_t /* verificationTokenTimeStamp */,
+ int /* verificationTokenSecurityLevel */,
+ const uint8_t* /* verificationTokenMac */,
+ size_t /* verificationTokenMacSize */) {
+ // Here's where we would validate the passed-in |authToken| to assure
+ // ourselves that it comes from the e.g. biometric hardware and wasn't made up
+ // by an attacker.
+ //
+ // However this involves calculating the MAC which requires access to the to
+ // a pre-shared key which we don't have...
+ //
+ return true;
+}
+
+bool eicOpsX509GetPublicKey(const uint8_t* x509Cert, size_t x509CertSize,
+ uint8_t* publicKey, size_t* publicKeySize) {
+ vector<uint8_t> chain;
+ chain.resize(x509CertSize);
+ memcpy(chain.data(), x509Cert, x509CertSize);
+ optional<vector<uint8_t>> res =
+ android::hardware::identity::support::certificateChainGetTopMostKey(
+ chain);
+ if (!res) {
+ return false;
+ }
+ if (res.value().size() > *publicKeySize) {
+ eicDebug("Public key size is %zd but buffer only has room for %zd bytes",
+ res.value().size(), *publicKeySize);
+ return false;
+ }
+ *publicKeySize = res.value().size();
+ memcpy(publicKey, res.value().data(), *publicKeySize);
+ eicDebug("Extracted %zd bytes public key from %zd bytes X.509 cert",
+ *publicKeySize, x509CertSize);
+ return true;
+}
+
+bool eicOpsX509CertSignedByPublicKey(const uint8_t* x509Cert,
+ size_t x509CertSize,
+ const uint8_t* publicKey,
+ size_t publicKeySize) {
+ vector<uint8_t> certVec(x509Cert, x509Cert + x509CertSize);
+ vector<uint8_t> publicKeyVec(publicKey, publicKey + publicKeySize);
+ return android::hardware::identity::support::certificateSignedByPublicKey(
+ certVec, publicKeyVec);
+}
+
+bool eicOpsEcDsaVerifyWithPublicKey(const uint8_t* digest, size_t digestSize,
+ const uint8_t* signature,
+ size_t signatureSize,
+ const uint8_t* publicKey,
+ size_t publicKeySize) {
+ vector<uint8_t> digestVec(digest, digest + digestSize);
+ vector<uint8_t> signatureVec(signature, signature + signatureSize);
+ vector<uint8_t> publicKeyVec(publicKey, publicKey + publicKeySize);
+
+ vector<uint8_t> derSignature;
+ if (!android::hardware::identity::support::ecdsaSignatureCoseToDer(
+ signatureVec, derSignature)) {
+ LOG(ERROR) << "Error convering signature to DER format";
+ return false;
+ }
+
+ if (!android::hardware::identity::support::checkEcDsaSignature(
+ digestVec, derSignature, publicKeyVec)) {
+ LOG(ERROR) << "Signature check failed";
+ return false;
+ }
+ return true;
+}
+
+bool eicOpsEcdh(const uint8_t publicKey[EIC_P256_PUB_KEY_SIZE],
+ const uint8_t privateKey[EIC_P256_PUB_KEY_SIZE],
+ uint8_t sharedSecret[EIC_P256_COORDINATE_SIZE]) {
+ vector<uint8_t> pubKeyVec(EIC_P256_PUB_KEY_SIZE + 1);
+ pubKeyVec[0] = 0x04;
+ memcpy(pubKeyVec.data() + 1, publicKey, EIC_P256_PUB_KEY_SIZE);
+
+ vector<uint8_t> privKeyVec(EIC_P256_PRIV_KEY_SIZE);
+ memcpy(privKeyVec.data(), privateKey, EIC_P256_PRIV_KEY_SIZE);
+
+ optional<vector<uint8_t>> shared =
+ android::hardware::identity::support::ecdh(pubKeyVec, privKeyVec);
+ if (!shared) {
+ LOG(ERROR) << "Error performing ECDH";
+ return false;
+ }
+ if (shared.value().size() != EIC_P256_COORDINATE_SIZE) {
+ LOG(ERROR) << "Unexpected size of shared secret " << shared.value().size()
+ << " expected " << EIC_P256_COORDINATE_SIZE << " bytes";
+ return false;
+ }
+ memcpy(sharedSecret, shared.value().data(), EIC_P256_COORDINATE_SIZE);
+ return true;
+}
+
+bool eicOpsHkdf(const uint8_t* sharedSecret, size_t sharedSecretSize,
+ const uint8_t* salt, size_t saltSize, const uint8_t* info,
+ size_t infoSize, uint8_t* output, size_t outputSize) {
+ vector<uint8_t> sharedSecretVec(sharedSecretSize);
+ memcpy(sharedSecretVec.data(), sharedSecret, sharedSecretSize);
+ vector<uint8_t> saltVec(saltSize);
+ memcpy(saltVec.data(), salt, saltSize);
+ vector<uint8_t> infoVec(infoSize);
+ memcpy(infoVec.data(), info, infoSize);
+
+ optional<vector<uint8_t>> result = android::hardware::identity::support::hkdf(
+ sharedSecretVec, saltVec, infoVec, outputSize);
+ if (!result) {
+ LOG(ERROR) << "Error performing HKDF";
+ return false;
+ }
+ if (result.value().size() != outputSize) {
+ LOG(ERROR) << "Unexpected size of HKDF " << result.value().size()
+ << " expected " << outputSize;
+ return false;
+ }
+ memcpy(output, result.value().data(), outputSize);
+ return true;
+}
+
+#ifdef EIC_DEBUG
+
+void eicPrint(const char* format, ...) {
+ va_list args;
+ va_start(args, format);
+ vfprintf(stderr, format, args);
+ va_end(args);
+}
+
+void eicHexdump(const char* message, const uint8_t* data, size_t dataSize) {
+ vector<uint8_t> dataVec(dataSize);
+ memcpy(dataVec.data(), data, dataSize);
+ android::hardware::identity::support::hexdump(message, dataVec);
+}
+
+void eicCborPrettyPrint(const uint8_t* cborData, size_t cborDataSize,
+ size_t maxBStrSize) {
+ vector<uint8_t> cborDataVec(cborDataSize);
+ memcpy(cborDataVec.data(), cborData, cborDataSize);
+ string str = android::hardware::identity::support::cborPrettyPrint(
+ cborDataVec, maxBStrSize, {});
+ fprintf(stderr, "%s\n", str.c_str());
+}
+
+#endif // EIC_DEBUG
diff --git a/guest/hals/identity/libeic/EicOpsImpl.h b/guest/hals/identity/libeic/EicOpsImpl.h
new file mode 100644
index 0000000..333cdce
--- /dev/null
+++ b/guest/hals/identity/libeic/EicOpsImpl.h
@@ -0,0 +1,46 @@
+/*
+ * Copyright 2020, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef ANDROID_HARDWARE_IDENTITY_EIC_OPS_IMPL_H
+#define ANDROID_HARDWARE_IDENTITY_EIC_OPS_IMPL_H
+
+#include <stdbool.h>
+#include <stddef.h>
+#include <stdlib.h>
+
+// Add whatever includes are needed for definitions below.
+//
+
+#include <openssl/hmac.h>
+#include <openssl/sha.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+// Set the following defines to match the implementation of the supplied
+// eicOps*() operations. See EicOps.h for details.
+//
+
+#define EIC_SHA256_CONTEXT_SIZE sizeof(SHA256_CTX)
+
+#define EIC_HMAC_SHA256_CONTEXT_SIZE sizeof(HMAC_CTX)
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif // ANDROID_HARDWARE_IDENTITY_EMBEDDED_IC_H
diff --git a/guest/hals/identity/libeic/EicPresentation.c b/guest/hals/identity/libeic/EicPresentation.c
new file mode 100644
index 0000000..520c2c2
--- /dev/null
+++ b/guest/hals/identity/libeic/EicPresentation.c
@@ -0,0 +1,916 @@
+/*
+ * Copyright 2020, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "EicPresentation.h"
+#include "EicCommon.h"
+
+#include <inttypes.h>
+
+bool eicPresentationInit(EicPresentation* ctx, bool testCredential,
+ const char* docType, size_t docTypeLength,
+ const uint8_t* encryptedCredentialKeys,
+ size_t encryptedCredentialKeysSize) {
+ uint8_t credentialKeys[EIC_CREDENTIAL_KEYS_CBOR_SIZE_FEATURE_VERSION_202101];
+ bool expectPopSha256 = false;
+
+ // For feature version 202009 it's 52 bytes long and for feature version
+ // 202101 it's 86 bytes (the additional data is the ProofOfProvisioning
+ // SHA-256). We need to support loading all feature versions.
+ //
+ if (encryptedCredentialKeysSize ==
+ EIC_CREDENTIAL_KEYS_CBOR_SIZE_FEATURE_VERSION_202009 + 28) {
+ /* do nothing */
+ } else if (encryptedCredentialKeysSize ==
+ EIC_CREDENTIAL_KEYS_CBOR_SIZE_FEATURE_VERSION_202101 + 28) {
+ expectPopSha256 = true;
+ } else {
+ eicDebug("Unexpected size %zd for encryptedCredentialKeys",
+ encryptedCredentialKeysSize);
+ return false;
+ }
+
+ eicMemSet(ctx, '\0', sizeof(EicPresentation));
+
+ if (!eicOpsDecryptAes128Gcm(
+ eicOpsGetHardwareBoundKey(testCredential), encryptedCredentialKeys,
+ encryptedCredentialKeysSize,
+ // DocType is the additionalAuthenticatedData
+ (const uint8_t*)docType, docTypeLength, credentialKeys)) {
+ eicDebug("Error decrypting CredentialKeys");
+ return false;
+ }
+
+ // It's supposed to look like this;
+ //
+ // Feature version 202009:
+ //
+ // CredentialKeys = [
+ // bstr, ; storageKey, a 128-bit AES key
+ // bstr, ; credentialPrivKey, the private key for credentialKey
+ // ]
+ //
+ // Feature version 202101:
+ //
+ // CredentialKeys = [
+ // bstr, ; storageKey, a 128-bit AES key
+ // bstr, ; credentialPrivKey, the private key for credentialKey
+ // bstr ; proofOfProvisioning SHA-256
+ // ]
+ //
+ // where storageKey is 16 bytes, credentialPrivateKey is 32 bytes, and
+ // proofOfProvisioning SHA-256 is 32 bytes.
+ //
+ if (credentialKeys[0] !=
+ (expectPopSha256 ? 0x83 : 0x82) || // array of two or three elements
+ credentialKeys[1] != 0x50 || // 16-byte bstr
+ credentialKeys[18] != 0x58 ||
+ credentialKeys[19] != 0x20) { // 32-byte bstr
+ eicDebug("Invalid CBOR for CredentialKeys");
+ return false;
+ }
+ if (expectPopSha256) {
+ if (credentialKeys[52] != 0x58 ||
+ credentialKeys[53] != 0x20) { // 32-byte bstr
+ eicDebug("Invalid CBOR for CredentialKeys");
+ return false;
+ }
+ }
+ eicMemCpy(ctx->storageKey, credentialKeys + 2, EIC_AES_128_KEY_SIZE);
+ eicMemCpy(ctx->credentialPrivateKey, credentialKeys + 20,
+ EIC_P256_PRIV_KEY_SIZE);
+ ctx->testCredential = testCredential;
+ if (expectPopSha256) {
+ eicMemCpy(ctx->proofOfProvisioningSha256, credentialKeys + 54,
+ EIC_SHA256_DIGEST_SIZE);
+ }
+ return true;
+}
+
+bool eicPresentationGenerateSigningKeyPair(EicPresentation* ctx,
+ const char* docType,
+ size_t docTypeLength, time_t now,
+ uint8_t* publicKeyCert,
+ size_t* publicKeyCertSize,
+ uint8_t signingKeyBlob[60]) {
+ uint8_t signingKeyPriv[EIC_P256_PRIV_KEY_SIZE];
+ uint8_t signingKeyPub[EIC_P256_PUB_KEY_SIZE];
+ uint8_t cborBuf[64];
+
+ // Generate the ProofOfBinding CBOR to include in the X.509 certificate in
+ // IdentityCredentialAuthenticationKeyExtension CBOR. This CBOR is defined
+ // by the following CDDL
+ //
+ // ProofOfBinding = [
+ // "ProofOfBinding",
+ // bstr, // Contains the SHA-256 of ProofOfProvisioning
+ // ]
+ //
+ // This array may grow in the future if other information needs to be
+ // conveyed.
+ //
+ // The bytes of ProofOfBinding is is represented as an OCTET_STRING
+ // and stored at OID 1.3.6.1.4.1.11129.2.1.26.
+ //
+
+ EicCbor cbor;
+ eicCborInit(&cbor, cborBuf, sizeof cborBuf);
+ eicCborAppendArray(&cbor, 2);
+ eicCborAppendStringZ(&cbor, "ProofOfBinding");
+ eicCborAppendByteString(&cbor, ctx->proofOfProvisioningSha256,
+ EIC_SHA256_DIGEST_SIZE);
+ if (cbor.size > sizeof(cborBuf)) {
+ eicDebug("Exceeded buffer size");
+ return false;
+ }
+ const uint8_t* proofOfBinding = cborBuf;
+ size_t proofOfBindingSize = cbor.size;
+
+ if (!eicOpsCreateEcKey(signingKeyPriv, signingKeyPub)) {
+ eicDebug("Error creating signing key");
+ return false;
+ }
+
+ const int secondsInOneYear = 365 * 24 * 60 * 60;
+ time_t validityNotBefore = now;
+ time_t validityNotAfter = now + secondsInOneYear; // One year from now.
+ if (!eicOpsSignEcKey(
+ signingKeyPub, ctx->credentialPrivateKey, 1,
+ "Android Identity Credential Key", // issuer CN
+ "Android Identity Credential Authentication Key", // subject CN
+ validityNotBefore, validityNotAfter, proofOfBinding,
+ proofOfBindingSize, publicKeyCert, publicKeyCertSize)) {
+ eicDebug("Error creating certificate for signing key");
+ return false;
+ }
+
+ uint8_t nonce[12];
+ if (!eicOpsRandom(nonce, 12)) {
+ eicDebug("Error getting random");
+ return false;
+ }
+ if (!eicOpsEncryptAes128Gcm(
+ ctx->storageKey, nonce, signingKeyPriv, sizeof(signingKeyPriv),
+ // DocType is the additionalAuthenticatedData
+ (const uint8_t*)docType, docTypeLength, signingKeyBlob)) {
+ eicDebug("Error encrypting signing key");
+ return false;
+ }
+
+ return true;
+}
+
+bool eicPresentationCreateEphemeralKeyPair(
+ EicPresentation* ctx, uint8_t ephemeralPrivateKey[EIC_P256_PRIV_KEY_SIZE]) {
+ uint8_t ephemeralPublicKey[EIC_P256_PUB_KEY_SIZE];
+ if (!eicOpsCreateEcKey(ctx->ephemeralPrivateKey, ephemeralPublicKey)) {
+ eicDebug("Error creating ephemeral key");
+ return false;
+ }
+ eicMemCpy(ephemeralPrivateKey, ctx->ephemeralPrivateKey,
+ EIC_P256_PRIV_KEY_SIZE);
+ return true;
+}
+
+bool eicPresentationCreateAuthChallenge(EicPresentation* ctx,
+ uint64_t* authChallenge) {
+ do {
+ if (!eicOpsRandom((uint8_t*)&(ctx->authChallenge), sizeof(uint64_t))) {
+ eicDebug("Failed generating random challenge");
+ return false;
+ }
+ } while (ctx->authChallenge == 0);
+ eicDebug("Created auth challenge %" PRIu64, ctx->authChallenge);
+ *authChallenge = ctx->authChallenge;
+ return true;
+}
+
+// From "COSE Algorithms" registry
+//
+#define COSE_ALG_ECDSA_256 -7
+
+bool eicPresentationValidateRequestMessage(
+ EicPresentation* ctx, const uint8_t* sessionTranscript,
+ size_t sessionTranscriptSize, const uint8_t* requestMessage,
+ size_t requestMessageSize, int coseSignAlg,
+ const uint8_t* readerSignatureOfToBeSigned,
+ size_t readerSignatureOfToBeSignedSize) {
+ if (ctx->readerPublicKeySize == 0) {
+ eicDebug("No public key for reader");
+ return false;
+ }
+
+ // Right now we only support ECDSA with SHA-256 (e.g. ES256).
+ //
+ if (coseSignAlg != COSE_ALG_ECDSA_256) {
+ eicDebug(
+ "COSE Signature algorithm for reader signature is %d, "
+ "only ECDSA with SHA-256 is supported right now",
+ coseSignAlg);
+ return false;
+ }
+
+ // What we're going to verify is the COSE ToBeSigned structure which
+ // looks like the following:
+ //
+ // Sig_structure = [
+ // context : "Signature" / "Signature1" / "CounterSignature",
+ // body_protected : empty_or_serialized_map,
+ // ? sign_protected : empty_or_serialized_map,
+ // external_aad : bstr,
+ // payload : bstr
+ // ]
+ //
+ // So we're going to build that CBOR...
+ //
+ EicCbor cbor;
+ eicCborInit(&cbor, NULL, 0);
+ eicCborAppendArray(&cbor, 4);
+ eicCborAppendStringZ(&cbor, "Signature1");
+
+ // The COSE Encoded protected headers is just a single field with
+ // COSE_LABEL_ALG (1) -> coseSignAlg (e.g. -7). For simplicitly we just
+ // hard-code the CBOR encoding:
+ static const uint8_t coseEncodedProtectedHeaders[] = {0xa1, 0x01, 0x26};
+ eicCborAppendByteString(&cbor, coseEncodedProtectedHeaders,
+ sizeof(coseEncodedProtectedHeaders));
+
+ // External_aad is the empty bstr
+ static const uint8_t externalAad[0] = {};
+ eicCborAppendByteString(&cbor, externalAad, sizeof(externalAad));
+
+ // For the payload, the _encoded_ form follows here. We handle this by simply
+ // opening a bstr, and then writing the CBOR. This requires us to know the
+ // size of said bstr, ahead of time... the CBOR to be written is
+ //
+ // ReaderAuthentication = [
+ // "ReaderAuthentication",
+ // SessionTranscript,
+ // ItemsRequestBytes
+ // ]
+ //
+ // ItemsRequestBytes = #6.24(bstr .cbor ItemsRequest)
+ //
+ // ReaderAuthenticationBytes = #6.24(bstr .cbor ReaderAuthentication)
+ //
+ // which is easily calculated below
+ //
+ size_t calculatedSize = 0;
+ calculatedSize += 1; // Array of size 3
+ calculatedSize += 1; // "ReaderAuthentication" less than 24 bytes
+ calculatedSize +=
+ sizeof("ReaderAuthentication") - 1; // Don't include trailing NUL
+ calculatedSize += sessionTranscriptSize; // Already CBOR encoded
+ calculatedSize += 2; // Semantic tag EIC_CBOR_SEMANTIC_TAG_ENCODED_CBOR (24)
+ calculatedSize += 1 + eicCborAdditionalLengthBytesFor(requestMessageSize);
+ calculatedSize += requestMessageSize;
+
+ // However note that we're authenticating ReaderAuthenticationBytes which
+ // is a tagged bstr of the bytes of ReaderAuthentication. So need to get
+ // that in front.
+ size_t rabCalculatedSize = 0;
+ rabCalculatedSize +=
+ 2; // Semantic tag EIC_CBOR_SEMANTIC_TAG_ENCODED_CBOR (24)
+ rabCalculatedSize += 1 + eicCborAdditionalLengthBytesFor(calculatedSize);
+ rabCalculatedSize += calculatedSize;
+
+ // Begin the bytestring for ReaderAuthenticationBytes;
+ eicCborBegin(&cbor, EIC_CBOR_MAJOR_TYPE_BYTE_STRING, rabCalculatedSize);
+
+ eicCborAppendSemantic(&cbor, EIC_CBOR_SEMANTIC_TAG_ENCODED_CBOR);
+
+ // Begins the bytestring for ReaderAuthentication;
+ eicCborBegin(&cbor, EIC_CBOR_MAJOR_TYPE_BYTE_STRING, calculatedSize);
+
+ // And now that we know the size, let's fill it in...
+ //
+ size_t payloadOffset = cbor.size;
+ eicCborBegin(&cbor, EIC_CBOR_MAJOR_TYPE_ARRAY, 3);
+ eicCborAppendStringZ(&cbor, "ReaderAuthentication");
+ eicCborAppend(&cbor, sessionTranscript, sessionTranscriptSize);
+ eicCborAppendSemantic(&cbor, EIC_CBOR_SEMANTIC_TAG_ENCODED_CBOR);
+ eicCborBegin(&cbor, EIC_CBOR_MAJOR_TYPE_BYTE_STRING, requestMessageSize);
+ eicCborAppend(&cbor, requestMessage, requestMessageSize);
+
+ if (cbor.size != payloadOffset + calculatedSize) {
+ eicDebug("CBOR size is %zd but we expected %zd", cbor.size,
+ payloadOffset + calculatedSize);
+ return false;
+ }
+ uint8_t toBeSignedDigest[EIC_SHA256_DIGEST_SIZE];
+ eicCborFinal(&cbor, toBeSignedDigest);
+
+ if (!eicOpsEcDsaVerifyWithPublicKey(
+ toBeSignedDigest, EIC_SHA256_DIGEST_SIZE, readerSignatureOfToBeSigned,
+ readerSignatureOfToBeSignedSize, ctx->readerPublicKey,
+ ctx->readerPublicKeySize)) {
+ eicDebug("Request message is not signed by public key");
+ return false;
+ }
+ ctx->requestMessageValidated = true;
+ return true;
+}
+
+// Validates the next certificate in the reader certificate chain.
+bool eicPresentationPushReaderCert(EicPresentation* ctx,
+ const uint8_t* certX509,
+ size_t certX509Size) {
+ // If we had a previous certificate, use its public key to validate this
+ // certificate.
+ if (ctx->readerPublicKeySize > 0) {
+ if (!eicOpsX509CertSignedByPublicKey(certX509, certX509Size,
+ ctx->readerPublicKey,
+ ctx->readerPublicKeySize)) {
+ eicDebug(
+ "Certificate is not signed by public key in the previous "
+ "certificate");
+ return false;
+ }
+ }
+
+ // Store the key of this certificate, this is used to validate the next
+ // certificate and also ACPs with certificates that use the same public key...
+ ctx->readerPublicKeySize = EIC_PRESENTATION_MAX_READER_PUBLIC_KEY_SIZE;
+ if (!eicOpsX509GetPublicKey(certX509, certX509Size, ctx->readerPublicKey,
+ &ctx->readerPublicKeySize)) {
+ eicDebug("Error extracting public key from certificate");
+ return false;
+ }
+ if (ctx->readerPublicKeySize == 0) {
+ eicDebug("Zero-length public key in certificate");
+ return false;
+ }
+
+ return true;
+}
+
+bool eicPresentationSetAuthToken(
+ EicPresentation* ctx, uint64_t challenge, uint64_t secureUserId,
+ uint64_t authenticatorId, int hardwareAuthenticatorType, uint64_t timeStamp,
+ const uint8_t* mac, size_t macSize, uint64_t verificationTokenChallenge,
+ uint64_t verificationTokenTimestamp, int verificationTokenSecurityLevel,
+ const uint8_t* verificationTokenMac, size_t verificationTokenMacSize) {
+ // It doesn't make sense to accept any tokens if
+ // eicPresentationCreateAuthChallenge() was never called.
+ if (ctx->authChallenge == 0) {
+ eicDebug(
+ "Trying validate tokens when no auth-challenge was previously "
+ "generated");
+ return false;
+ }
+ // At least the verification-token must have the same challenge as what was
+ // generated.
+ if (verificationTokenChallenge != ctx->authChallenge) {
+ eicDebug(
+ "Challenge in verification token does not match the challenge "
+ "previously generated");
+ return false;
+ }
+ if (!eicOpsValidateAuthToken(
+ challenge, secureUserId, authenticatorId, hardwareAuthenticatorType,
+ timeStamp, mac, macSize, verificationTokenChallenge,
+ verificationTokenTimestamp, verificationTokenSecurityLevel,
+ verificationTokenMac, verificationTokenMacSize)) {
+ return false;
+ }
+ ctx->authTokenChallenge = challenge;
+ ctx->authTokenSecureUserId = secureUserId;
+ ctx->authTokenTimestamp = timeStamp;
+ ctx->verificationTokenTimestamp = verificationTokenTimestamp;
+ return true;
+}
+
+static bool checkUserAuth(EicPresentation* ctx, bool userAuthenticationRequired,
+ int timeoutMillis, uint64_t secureUserId) {
+ if (!userAuthenticationRequired) {
+ return true;
+ }
+
+ if (secureUserId != ctx->authTokenSecureUserId) {
+ eicDebug("secureUserId in profile differs from userId in authToken");
+ return false;
+ }
+
+ // Only ACP with auth-on-every-presentation - those with timeout == 0 - need
+ // the challenge to match...
+ if (timeoutMillis == 0) {
+ if (ctx->authTokenChallenge != ctx->authChallenge) {
+ eicDebug("Challenge in authToken (%" PRIu64
+ ") doesn't match the challenge "
+ "that was created (%" PRIu64 ") for this session",
+ ctx->authTokenChallenge, ctx->authChallenge);
+ return false;
+ }
+ }
+
+ uint64_t now = ctx->verificationTokenTimestamp;
+ if (ctx->authTokenTimestamp > now) {
+ eicDebug("Timestamp in authToken is in the future");
+ return false;
+ }
+
+ if (timeoutMillis > 0) {
+ if (now > ctx->authTokenTimestamp + timeoutMillis) {
+ eicDebug("Deadline for authToken is in the past");
+ return false;
+ }
+ }
+
+ return true;
+}
+
+static bool checkReaderAuth(EicPresentation* ctx,
+ const uint8_t* readerCertificate,
+ size_t readerCertificateSize) {
+ uint8_t publicKey[EIC_PRESENTATION_MAX_READER_PUBLIC_KEY_SIZE];
+ size_t publicKeySize;
+
+ if (readerCertificateSize == 0) {
+ return true;
+ }
+
+ // Remember in this case certificate equality is done by comparing public
+ // keys, not bitwise comparison of the certificates.
+ //
+ publicKeySize = EIC_PRESENTATION_MAX_READER_PUBLIC_KEY_SIZE;
+ if (!eicOpsX509GetPublicKey(readerCertificate, readerCertificateSize,
+ publicKey, &publicKeySize)) {
+ eicDebug("Error extracting public key from certificate");
+ return false;
+ }
+ if (publicKeySize == 0) {
+ eicDebug("Zero-length public key in certificate");
+ return false;
+ }
+
+ if ((ctx->readerPublicKeySize != publicKeySize) ||
+ (eicCryptoMemCmp(ctx->readerPublicKey, publicKey,
+ ctx->readerPublicKeySize) != 0)) {
+ return false;
+ }
+ return true;
+}
+
+// Note: This function returns false _only_ if an error occurred check for
+// access, _not_ whether access is granted. Whether access is granted is
+// returned in |accessGranted|.
+//
+bool eicPresentationValidateAccessControlProfile(
+ EicPresentation* ctx, int id, const uint8_t* readerCertificate,
+ size_t readerCertificateSize, bool userAuthenticationRequired,
+ int timeoutMillis, uint64_t secureUserId, const uint8_t mac[28],
+ bool* accessGranted, uint8_t* scratchSpace, size_t scratchSpaceSize) {
+ *accessGranted = false;
+ if (id < 0 || id >= 32) {
+ eicDebug("id value of %d is out of allowed range [0, 32[", id);
+ return false;
+ }
+
+ // Validate the MAC
+ EicCbor cborBuilder;
+ eicCborInit(&cborBuilder, scratchSpace, scratchSpaceSize);
+ if (!eicCborCalcAccessControl(
+ &cborBuilder, id, readerCertificate, readerCertificateSize,
+ userAuthenticationRequired, timeoutMillis, secureUserId)) {
+ return false;
+ }
+ if (!eicOpsDecryptAes128Gcm(ctx->storageKey, mac, 28, cborBuilder.buffer,
+ cborBuilder.size, NULL)) {
+ eicDebug("MAC for AccessControlProfile doesn't match");
+ return false;
+ }
+
+ bool passedUserAuth = checkUserAuth(ctx, userAuthenticationRequired,
+ timeoutMillis, secureUserId);
+ bool passedReaderAuth =
+ checkReaderAuth(ctx, readerCertificate, readerCertificateSize);
+
+ ctx->accessControlProfileMaskValidated |= (1U << id);
+ if (readerCertificateSize > 0) {
+ ctx->accessControlProfileMaskUsesReaderAuth |= (1U << id);
+ }
+ if (!passedReaderAuth) {
+ ctx->accessControlProfileMaskFailedReaderAuth |= (1U << id);
+ }
+ if (!passedUserAuth) {
+ ctx->accessControlProfileMaskFailedUserAuth |= (1U << id);
+ }
+
+ if (passedUserAuth && passedReaderAuth) {
+ *accessGranted = true;
+ eicDebug("Access granted for id %d", id);
+ }
+ return true;
+}
+
+bool eicPresentationCalcMacKey(
+ EicPresentation* ctx, const uint8_t* sessionTranscript,
+ size_t sessionTranscriptSize,
+ const uint8_t readerEphemeralPublicKey[EIC_P256_PUB_KEY_SIZE],
+ const uint8_t signingKeyBlob[60], const char* docType, size_t docTypeLength,
+ unsigned int numNamespacesWithValues, size_t expectedDeviceNamespacesSize) {
+ uint8_t signingKeyPriv[EIC_P256_PRIV_KEY_SIZE];
+ if (!eicOpsDecryptAes128Gcm(ctx->storageKey, signingKeyBlob, 60,
+ (const uint8_t*)docType, docTypeLength,
+ signingKeyPriv)) {
+ eicDebug("Error decrypting signingKeyBlob");
+ return false;
+ }
+
+ uint8_t sharedSecret[EIC_P256_COORDINATE_SIZE];
+ if (!eicOpsEcdh(readerEphemeralPublicKey, signingKeyPriv, sharedSecret)) {
+ eicDebug("ECDH failed");
+ return false;
+ }
+
+ EicCbor cbor;
+ eicCborInit(&cbor, NULL, 0);
+ eicCborAppendSemantic(&cbor, EIC_CBOR_SEMANTIC_TAG_ENCODED_CBOR);
+ eicCborAppendByteString(&cbor, sessionTranscript, sessionTranscriptSize);
+ uint8_t salt[EIC_SHA256_DIGEST_SIZE];
+ eicCborFinal(&cbor, salt);
+
+ const uint8_t info[7] = {'E', 'M', 'a', 'c', 'K', 'e', 'y'};
+ uint8_t derivedKey[32];
+ if (!eicOpsHkdf(sharedSecret, EIC_P256_COORDINATE_SIZE, salt, sizeof(salt),
+ info, sizeof(info), derivedKey, sizeof(derivedKey))) {
+ eicDebug("HKDF failed");
+ return false;
+ }
+
+ eicCborInitHmacSha256(&ctx->cbor, NULL, 0, derivedKey, sizeof(derivedKey));
+ ctx->buildCbor = true;
+
+ // What we're going to calculate the HMAC-SHA256 is the COSE ToBeMaced
+ // structure which looks like the following:
+ //
+ // MAC_structure = [
+ // context : "MAC" / "MAC0",
+ // protected : empty_or_serialized_map,
+ // external_aad : bstr,
+ // payload : bstr
+ // ]
+ //
+ eicCborAppendArray(&ctx->cbor, 4);
+ eicCborAppendStringZ(&ctx->cbor, "MAC0");
+
+ // The COSE Encoded protected headers is just a single field with
+ // COSE_LABEL_ALG (1) -> COSE_ALG_HMAC_256_256 (5). For simplicitly we just
+ // hard-code the CBOR encoding:
+ static const uint8_t coseEncodedProtectedHeaders[] = {0xa1, 0x01, 0x05};
+ eicCborAppendByteString(&ctx->cbor, coseEncodedProtectedHeaders,
+ sizeof(coseEncodedProtectedHeaders));
+
+ // We currently don't support Externally Supplied Data (RFC 8152 section 4.3)
+ // so external_aad is the empty bstr
+ static const uint8_t externalAad[0] = {};
+ eicCborAppendByteString(&ctx->cbor, externalAad, sizeof(externalAad));
+
+ // For the payload, the _encoded_ form follows here. We handle this by simply
+ // opening a bstr, and then writing the CBOR. This requires us to know the
+ // size of said bstr, ahead of time... the CBOR to be written is
+ //
+ // DeviceAuthentication = [
+ // "DeviceAuthentication",
+ // SessionTranscript,
+ // DocType, ; DocType as used in Documents structure in
+ // OfflineResponse DeviceNameSpacesBytes
+ // ]
+ //
+ // DeviceNameSpacesBytes = #6.24(bstr .cbor DeviceNameSpaces)
+ //
+ // DeviceAuthenticationBytes = #6.24(bstr .cbor DeviceAuthentication)
+ //
+ // which is easily calculated below
+ //
+ size_t calculatedSize = 0;
+ calculatedSize += 1; // Array of size 4
+ calculatedSize += 1; // "DeviceAuthentication" less than 24 bytes
+ calculatedSize +=
+ sizeof("DeviceAuthentication") - 1; // Don't include trailing NUL
+ calculatedSize += sessionTranscriptSize; // Already CBOR encoded
+ calculatedSize +=
+ 1 + eicCborAdditionalLengthBytesFor(docTypeLength) + docTypeLength;
+ calculatedSize += 2; // Semantic tag EIC_CBOR_SEMANTIC_TAG_ENCODED_CBOR (24)
+ calculatedSize +=
+ 1 + eicCborAdditionalLengthBytesFor(expectedDeviceNamespacesSize);
+ calculatedSize += expectedDeviceNamespacesSize;
+
+ // However note that we're authenticating DeviceAuthenticationBytes which
+ // is a tagged bstr of the bytes of DeviceAuthentication. So need to get
+ // that in front.
+ size_t dabCalculatedSize = 0;
+ dabCalculatedSize +=
+ 2; // Semantic tag EIC_CBOR_SEMANTIC_TAG_ENCODED_CBOR (24)
+ dabCalculatedSize += 1 + eicCborAdditionalLengthBytesFor(calculatedSize);
+ dabCalculatedSize += calculatedSize;
+
+ // Begin the bytestring for DeviceAuthenticationBytes;
+ eicCborBegin(&ctx->cbor, EIC_CBOR_MAJOR_TYPE_BYTE_STRING, dabCalculatedSize);
+
+ eicCborAppendSemantic(&ctx->cbor, EIC_CBOR_SEMANTIC_TAG_ENCODED_CBOR);
+
+ // Begins the bytestring for DeviceAuthentication;
+ eicCborBegin(&ctx->cbor, EIC_CBOR_MAJOR_TYPE_BYTE_STRING, calculatedSize);
+
+ eicCborAppendArray(&ctx->cbor, 4);
+ eicCborAppendStringZ(&ctx->cbor, "DeviceAuthentication");
+ eicCborAppend(&ctx->cbor, sessionTranscript, sessionTranscriptSize);
+ eicCborAppendString(&ctx->cbor, docType, docTypeLength);
+
+ // For the payload, the _encoded_ form follows here. We handle this by simply
+ // opening a bstr, and then writing the CBOR. This requires us to know the
+ // size of said bstr, ahead of time.
+ eicCborAppendSemantic(&ctx->cbor, EIC_CBOR_SEMANTIC_TAG_ENCODED_CBOR);
+ eicCborBegin(&ctx->cbor, EIC_CBOR_MAJOR_TYPE_BYTE_STRING,
+ expectedDeviceNamespacesSize);
+ ctx->expectedCborSizeAtEnd = expectedDeviceNamespacesSize + ctx->cbor.size;
+
+ eicCborAppendMap(&ctx->cbor, numNamespacesWithValues);
+ return true;
+}
+
+bool eicPresentationStartRetrieveEntries(EicPresentation* ctx) {
+ // HAL may use this object multiple times to retrieve data so need to reset
+ // various state objects here.
+ ctx->requestMessageValidated = false;
+ ctx->buildCbor = false;
+ ctx->accessControlProfileMaskValidated = 0;
+ ctx->accessControlProfileMaskUsesReaderAuth = 0;
+ ctx->accessControlProfileMaskFailedReaderAuth = 0;
+ ctx->accessControlProfileMaskFailedUserAuth = 0;
+ ctx->readerPublicKeySize = 0;
+ return true;
+}
+
+EicAccessCheckResult eicPresentationStartRetrieveEntryValue(
+ EicPresentation* ctx, const char* nameSpace, size_t nameSpaceLength,
+ const char* name, size_t nameLength, unsigned int newNamespaceNumEntries,
+ int32_t entrySize, const uint8_t* accessControlProfileIds,
+ size_t numAccessControlProfileIds, uint8_t* scratchSpace,
+ size_t scratchSpaceSize) {
+ (void)entrySize;
+ uint8_t* additionalDataCbor = scratchSpace;
+ size_t additionalDataCborBufferSize = scratchSpaceSize;
+ size_t additionalDataCborSize;
+
+ if (newNamespaceNumEntries > 0) {
+ eicCborAppendString(&ctx->cbor, nameSpace, nameSpaceLength);
+ eicCborAppendMap(&ctx->cbor, newNamespaceNumEntries);
+ }
+
+ // We'll need to calc and store a digest of additionalData to check that it's
+ // the same additionalData being passed in for every
+ // eicPresentationRetrieveEntryValue() call...
+ //
+ ctx->accessCheckOk = false;
+ if (!eicCborCalcEntryAdditionalData(
+ accessControlProfileIds, numAccessControlProfileIds, nameSpace,
+ nameSpaceLength, name, nameLength, additionalDataCbor,
+ additionalDataCborBufferSize, &additionalDataCborSize,
+ ctx->additionalDataSha256)) {
+ return EIC_ACCESS_CHECK_RESULT_FAILED;
+ }
+
+ if (numAccessControlProfileIds == 0) {
+ return EIC_ACCESS_CHECK_RESULT_NO_ACCESS_CONTROL_PROFILES;
+ }
+
+ // Access is granted if at least one of the profiles grants access.
+ //
+ // If an item is configured without any profiles, access is denied.
+ //
+ EicAccessCheckResult result = EIC_ACCESS_CHECK_RESULT_FAILED;
+ for (size_t n = 0; n < numAccessControlProfileIds; n++) {
+ int id = accessControlProfileIds[n];
+ uint32_t idBitMask = (1 << id);
+
+ // If the access control profile wasn't validated, this is an error and we
+ // fail immediately.
+ bool validated =
+ ((ctx->accessControlProfileMaskValidated & idBitMask) != 0);
+ if (!validated) {
+ eicDebug("No ACP for profile id %d", id);
+ return EIC_ACCESS_CHECK_RESULT_FAILED;
+ }
+
+ // Otherwise, we _did_ validate the profile. If none of the checks
+ // failed, we're done
+ bool failedUserAuth =
+ ((ctx->accessControlProfileMaskFailedUserAuth & idBitMask) != 0);
+ bool failedReaderAuth =
+ ((ctx->accessControlProfileMaskFailedReaderAuth & idBitMask) != 0);
+ if (!failedUserAuth && !failedReaderAuth) {
+ result = EIC_ACCESS_CHECK_RESULT_OK;
+ break;
+ }
+ // One of the checks failed, convey which one
+ if (failedUserAuth) {
+ result = EIC_ACCESS_CHECK_RESULT_USER_AUTHENTICATION_FAILED;
+ } else {
+ result = EIC_ACCESS_CHECK_RESULT_READER_AUTHENTICATION_FAILED;
+ }
+ }
+ eicDebug("Result %d for name %s", result, name);
+
+ if (result == EIC_ACCESS_CHECK_RESULT_OK) {
+ eicCborAppendString(&ctx->cbor, name, nameLength);
+ ctx->accessCheckOk = true;
+ }
+ return result;
+}
+
+// Note: |content| must be big enough to hold |encryptedContentSize| - 28 bytes.
+bool eicPresentationRetrieveEntryValue(
+ EicPresentation* ctx, const uint8_t* encryptedContent,
+ size_t encryptedContentSize, uint8_t* content, const char* nameSpace,
+ size_t nameSpaceLength, const char* name, size_t nameLength,
+ const uint8_t* accessControlProfileIds, size_t numAccessControlProfileIds,
+ uint8_t* scratchSpace, size_t scratchSpaceSize) {
+ uint8_t* additionalDataCbor = scratchSpace;
+ size_t additionalDataCborBufferSize = scratchSpaceSize;
+ size_t additionalDataCborSize;
+
+ uint8_t calculatedSha256[EIC_SHA256_DIGEST_SIZE];
+ if (!eicCborCalcEntryAdditionalData(
+ accessControlProfileIds, numAccessControlProfileIds, nameSpace,
+ nameSpaceLength, name, nameLength, additionalDataCbor,
+ additionalDataCborBufferSize, &additionalDataCborSize,
+ calculatedSha256)) {
+ return false;
+ }
+
+ if (eicCryptoMemCmp(calculatedSha256, ctx->additionalDataSha256,
+ EIC_SHA256_DIGEST_SIZE) != 0) {
+ eicDebug("SHA-256 mismatch of additionalData");
+ return false;
+ }
+ if (!ctx->accessCheckOk) {
+ eicDebug("Attempting to retrieve a value for which access is not granted");
+ return false;
+ }
+
+ if (!eicOpsDecryptAes128Gcm(ctx->storageKey, encryptedContent,
+ encryptedContentSize, additionalDataCbor,
+ additionalDataCborSize, content)) {
+ eicDebug("Error decrypting content");
+ return false;
+ }
+
+ eicCborAppend(&ctx->cbor, content, encryptedContentSize - 28);
+
+ return true;
+}
+
+bool eicPresentationFinishRetrieval(EicPresentation* ctx,
+ uint8_t* digestToBeMaced,
+ size_t* digestToBeMacedSize) {
+ if (!ctx->buildCbor) {
+ *digestToBeMacedSize = 0;
+ return true;
+ }
+ if (*digestToBeMacedSize != 32) {
+ return false;
+ }
+
+ // This verifies that the correct expectedDeviceNamespacesSize value was
+ // passed in at eicPresentationCalcMacKey() time.
+ if (ctx->cbor.size != ctx->expectedCborSizeAtEnd) {
+ eicDebug("CBOR size is %zd, was expecting %zd", ctx->cbor.size,
+ ctx->expectedCborSizeAtEnd);
+ return false;
+ }
+ eicCborFinal(&ctx->cbor, digestToBeMaced);
+ return true;
+}
+
+bool eicPresentationDeleteCredential(
+ EicPresentation* ctx, const char* docType, size_t docTypeLength,
+ const uint8_t* challenge, size_t challengeSize, bool includeChallenge,
+ size_t proofOfDeletionCborSize,
+ uint8_t signatureOfToBeSigned[EIC_ECDSA_P256_SIGNATURE_SIZE]) {
+ EicCbor cbor;
+
+ eicCborInit(&cbor, NULL, 0);
+
+ // What we're going to sign is the COSE ToBeSigned structure which
+ // looks like the following:
+ //
+ // Sig_structure = [
+ // context : "Signature" / "Signature1" / "CounterSignature",
+ // body_protected : empty_or_serialized_map,
+ // ? sign_protected : empty_or_serialized_map,
+ // external_aad : bstr,
+ // payload : bstr
+ // ]
+ //
+ eicCborAppendArray(&cbor, 4);
+ eicCborAppendStringZ(&cbor, "Signature1");
+
+ // The COSE Encoded protected headers is just a single field with
+ // COSE_LABEL_ALG (1) -> COSE_ALG_ECSDA_256 (-7). For simplicitly we just
+ // hard-code the CBOR encoding:
+ static const uint8_t coseEncodedProtectedHeaders[] = {0xa1, 0x01, 0x26};
+ eicCborAppendByteString(&cbor, coseEncodedProtectedHeaders,
+ sizeof(coseEncodedProtectedHeaders));
+
+ // We currently don't support Externally Supplied Data (RFC 8152 section 4.3)
+ // so external_aad is the empty bstr
+ static const uint8_t externalAad[0] = {};
+ eicCborAppendByteString(&cbor, externalAad, sizeof(externalAad));
+
+ // For the payload, the _encoded_ form follows here. We handle this by simply
+ // opening a bstr, and then writing the CBOR. This requires us to know the
+ // size of said bstr, ahead of time.
+ eicCborBegin(&cbor, EIC_CBOR_MAJOR_TYPE_BYTE_STRING, proofOfDeletionCborSize);
+
+ // Finally, the CBOR that we're actually signing.
+ eicCborAppendArray(&cbor, includeChallenge ? 4 : 3);
+ eicCborAppendStringZ(&cbor, "ProofOfDeletion");
+ eicCborAppendString(&cbor, docType, docTypeLength);
+ if (includeChallenge) {
+ eicCborAppendByteString(&cbor, challenge, challengeSize);
+ }
+ eicCborAppendBool(&cbor, ctx->testCredential);
+
+ uint8_t cborSha256[EIC_SHA256_DIGEST_SIZE];
+ eicCborFinal(&cbor, cborSha256);
+ if (!eicOpsEcDsa(ctx->credentialPrivateKey, cborSha256,
+ signatureOfToBeSigned)) {
+ eicDebug("Error signing proofOfDeletion");
+ return false;
+ }
+
+ return true;
+}
+
+bool eicPresentationProveOwnership(
+ EicPresentation* ctx, const char* docType, size_t docTypeLength,
+ bool testCredential, const uint8_t* challenge, size_t challengeSize,
+ size_t proofOfOwnershipCborSize,
+ uint8_t signatureOfToBeSigned[EIC_ECDSA_P256_SIGNATURE_SIZE]) {
+ EicCbor cbor;
+
+ eicCborInit(&cbor, NULL, 0);
+
+ // What we're going to sign is the COSE ToBeSigned structure which
+ // looks like the following:
+ //
+ // Sig_structure = [
+ // context : "Signature" / "Signature1" / "CounterSignature",
+ // body_protected : empty_or_serialized_map,
+ // ? sign_protected : empty_or_serialized_map,
+ // external_aad : bstr,
+ // payload : bstr
+ // ]
+ //
+ eicCborAppendArray(&cbor, 4);
+ eicCborAppendStringZ(&cbor, "Signature1");
+
+ // The COSE Encoded protected headers is just a single field with
+ // COSE_LABEL_ALG (1) -> COSE_ALG_ECSDA_256 (-7). For simplicitly we just
+ // hard-code the CBOR encoding:
+ static const uint8_t coseEncodedProtectedHeaders[] = {0xa1, 0x01, 0x26};
+ eicCborAppendByteString(&cbor, coseEncodedProtectedHeaders,
+ sizeof(coseEncodedProtectedHeaders));
+
+ // We currently don't support Externally Supplied Data (RFC 8152 section 4.3)
+ // so external_aad is the empty bstr
+ static const uint8_t externalAad[0] = {};
+ eicCborAppendByteString(&cbor, externalAad, sizeof(externalAad));
+
+ // For the payload, the _encoded_ form follows here. We handle this by simply
+ // opening a bstr, and then writing the CBOR. This requires us to know the
+ // size of said bstr, ahead of time.
+ eicCborBegin(&cbor, EIC_CBOR_MAJOR_TYPE_BYTE_STRING,
+ proofOfOwnershipCborSize);
+
+ // Finally, the CBOR that we're actually signing.
+ eicCborAppendArray(&cbor, 4);
+ eicCborAppendStringZ(&cbor, "ProofOfOwnership");
+ eicCborAppendString(&cbor, docType, docTypeLength);
+ eicCborAppendByteString(&cbor, challenge, challengeSize);
+ eicCborAppendBool(&cbor, testCredential);
+
+ uint8_t cborSha256[EIC_SHA256_DIGEST_SIZE];
+ eicCborFinal(&cbor, cborSha256);
+ if (!eicOpsEcDsa(ctx->credentialPrivateKey, cborSha256,
+ signatureOfToBeSigned)) {
+ eicDebug("Error signing proofOfDeletion");
+ return false;
+ }
+
+ return true;
+}
diff --git a/guest/hals/identity/libeic/EicPresentation.h b/guest/hals/identity/libeic/EicPresentation.h
new file mode 100644
index 0000000..72d8401
--- /dev/null
+++ b/guest/hals/identity/libeic/EicPresentation.h
@@ -0,0 +1,264 @@
+/*
+ * Copyright 2020, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef ANDROID_HARDWARE_IDENTITY_EIC_PRESENTATION_H
+#define ANDROID_HARDWARE_IDENTITY_EIC_PRESENTATION_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include "EicCbor.h"
+
+// The maximum size we support for public keys in reader certificates.
+#define EIC_PRESENTATION_MAX_READER_PUBLIC_KEY_SIZE 65
+
+typedef struct {
+ int featureLevel;
+
+ uint8_t storageKey[EIC_AES_128_KEY_SIZE];
+ uint8_t credentialPrivateKey[EIC_P256_PRIV_KEY_SIZE];
+
+ uint8_t ephemeralPrivateKey[EIC_P256_PRIV_KEY_SIZE];
+
+ // The challenge generated with eicPresentationCreateAuthChallenge()
+ uint64_t authChallenge;
+
+ // Set by eicPresentationSetAuthToken() and contains the fields
+ // from the passed in authToken and verificationToken.
+ //
+ uint64_t authTokenChallenge;
+ uint64_t authTokenSecureUserId;
+ uint64_t authTokenTimestamp;
+ uint64_t verificationTokenTimestamp;
+
+ // The public key for the reader.
+ //
+ // (During the process of pushing reader certificates, this is also used to
+ // store the public key of the previously pushed certificate.)
+ //
+ uint8_t readerPublicKey[EIC_PRESENTATION_MAX_READER_PUBLIC_KEY_SIZE];
+ size_t readerPublicKeySize;
+
+ // This is set to true only if eicPresentationValidateRequestMessage()
+ // successfully validated the requestMessage.
+ //
+ // Why even record this? Because there's no requirement the HAL actually calls
+ // that function and we validate ACPs before it's called... so it's possible
+ // that a compromised HAL could trick us into marking ACPs as authorized while
+ // they in fact aren't.
+ bool requestMessageValidated;
+ bool buildCbor;
+
+ // Set to true initialized as a test credential.
+ bool testCredential;
+
+ // Set to true if the evaluation of access control checks in
+ // eicPresentationStartRetrieveEntryValue() resulted
+ // EIC_ACCESS_CHECK_RESULT_OK
+ bool accessCheckOk;
+
+ // These are bitmasks indicating which of the possible 32 access control
+ // profiles are authorized. They are built up by
+ // eicPresentationValidateAccessControlProfile().
+ //
+ uint32_t
+ accessControlProfileMaskValidated; // True if the profile was validated.
+ uint32_t accessControlProfileMaskUsesReaderAuth; // True if the ACP is using
+ // reader auth
+ uint32_t
+ accessControlProfileMaskFailedReaderAuth; // True if failed reader auth
+ uint32_t accessControlProfileMaskFailedUserAuth; // True if failed user auth
+
+ // SHA-256 for AdditionalData, updated for each entry.
+ uint8_t additionalDataSha256[EIC_SHA256_DIGEST_SIZE];
+
+ // SHA-256 of ProofOfProvisioning. Set to NUL-bytes or initialized from
+ // CredentialKeys data if credential was created with feature version 202101
+ // or later.
+ uint8_t proofOfProvisioningSha256[EIC_SHA256_DIGEST_SIZE];
+
+ size_t expectedCborSizeAtEnd;
+ EicCbor cbor;
+} EicPresentation;
+
+bool eicPresentationInit(EicPresentation* ctx, bool testCredential,
+ const char* docType, size_t docTypeLength,
+ const uint8_t* encryptedCredentialKeys,
+ size_t encryptedCredentialKeysSize);
+
+bool eicPresentationGenerateSigningKeyPair(EicPresentation* ctx,
+ const char* docType,
+ size_t docTypeLength, time_t now,
+ uint8_t* publicKeyCert,
+ size_t* publicKeyCertSize,
+ uint8_t signingKeyBlob[60]);
+
+// Create an ephemeral key-pair.
+//
+// The private key is stored in |ctx->ephemeralPrivateKey| and also returned in
+// |ephemeralPrivateKey|.
+//
+bool eicPresentationCreateEphemeralKeyPair(
+ EicPresentation* ctx, uint8_t ephemeralPrivateKey[EIC_P256_PRIV_KEY_SIZE]);
+
+// Returns a non-zero challenge in |authChallenge|.
+bool eicPresentationCreateAuthChallenge(EicPresentation* ctx,
+ uint64_t* authChallenge);
+
+// Starts retrieving entries.
+//
+bool eicPresentationStartRetrieveEntries(EicPresentation* ctx);
+
+// Sets the auth-token.
+bool eicPresentationSetAuthToken(
+ EicPresentation* ctx, uint64_t challenge, uint64_t secureUserId,
+ uint64_t authenticatorId, int hardwareAuthenticatorType, uint64_t timeStamp,
+ const uint8_t* mac, size_t macSize, uint64_t verificationTokenChallenge,
+ uint64_t verificationTokenTimeStamp, int verificationTokenSecurityLevel,
+ const uint8_t* verificationTokenMac, size_t verificationTokenMacSize);
+
+// Function to push certificates in the reader certificate chain.
+//
+// This should start with the root certificate (e.g. the last in the chain) and
+// continue up the chain, ending with the certificate for the reader.
+//
+// Calls to this function should be interleaved with calls to the
+// eicPresentationValidateAccessControlProfile() function, see below.
+//
+bool eicPresentationPushReaderCert(EicPresentation* ctx,
+ const uint8_t* certX509,
+ size_t certX509Size);
+
+// Checks an access control profile.
+//
+// Returns false if an error occurred while checking the profile (e.g. MAC
+// doesn't check out).
+//
+// Returns in |accessGranted| whether access is granted.
+//
+// If |readerCertificate| is non-empty and the public key of one of those
+// certificates appear in the chain presented by the reader, this function must
+// be called after pushing that certificate using
+// eicPresentationPushReaderCert().
+//
+// The scratchSpace should be set to a buffer at least 512 bytes. It's done
+// this way to avoid allocating stack space.
+//
+bool eicPresentationValidateAccessControlProfile(
+ EicPresentation* ctx, int id, const uint8_t* readerCertificate,
+ size_t readerCertificateSize, bool userAuthenticationRequired,
+ int timeoutMillis, uint64_t secureUserId, const uint8_t mac[28],
+ bool* accessGranted, uint8_t* scratchSpace, size_t scratchSpaceSize);
+
+// Validates that the given requestMessage is signed by the public key in the
+// certificate last set with eicPresentationPushReaderCert().
+//
+// The format of the signature is the same encoding as the 'signature' field of
+// COSE_Sign1 - that is, it's the R and S integers both with the same length as
+// the key-size.
+//
+// Must be called after eicPresentationPushReaderCert() have been used to push
+// the final certificate. Which is the certificate of the reader itself.
+//
+bool eicPresentationValidateRequestMessage(
+ EicPresentation* ctx, const uint8_t* sessionTranscript,
+ size_t sessionTranscriptSize, const uint8_t* requestMessage,
+ size_t requestMessageSize, int coseSignAlg,
+ const uint8_t* readerSignatureOfToBeSigned,
+ size_t readerSignatureOfToBeSignedSize);
+
+typedef enum {
+ // Returned if access is granted.
+ EIC_ACCESS_CHECK_RESULT_OK,
+
+ // Returned if an error occurred checking for access.
+ EIC_ACCESS_CHECK_RESULT_FAILED,
+
+ // Returned if access was denied because item is configured without any
+ // access control profiles.
+ EIC_ACCESS_CHECK_RESULT_NO_ACCESS_CONTROL_PROFILES,
+
+ // Returned if access was denied because of user authentication.
+ EIC_ACCESS_CHECK_RESULT_USER_AUTHENTICATION_FAILED,
+
+ // Returned if access was denied because of reader authentication.
+ EIC_ACCESS_CHECK_RESULT_READER_AUTHENTICATION_FAILED,
+} EicAccessCheckResult;
+
+// Passes enough information to calculate the MACing key
+//
+bool eicPresentationCalcMacKey(
+ EicPresentation* ctx, const uint8_t* sessionTranscript,
+ size_t sessionTranscriptSize,
+ const uint8_t readerEphemeralPublicKey[EIC_P256_PUB_KEY_SIZE],
+ const uint8_t signingKeyBlob[60], const char* docType, size_t docTypeLength,
+ unsigned int numNamespacesWithValues, size_t expectedDeviceNamespacesSize);
+
+// The scratchSpace should be set to a buffer at least 512 bytes (ideally 1024
+// bytes, the bigger the better). It's done this way to avoid allocating stack
+// space.
+//
+EicAccessCheckResult eicPresentationStartRetrieveEntryValue(
+ EicPresentation* ctx, const char* nameSpace, size_t nameSpaceLength,
+ const char* name, size_t nameLength, unsigned int newNamespaceNumEntries,
+ int32_t entrySize, const uint8_t* accessControlProfileIds,
+ size_t numAccessControlProfileIds, uint8_t* scratchSpace,
+ size_t scratchSpaceSize);
+
+// Note: |content| must be big enough to hold |encryptedContentSize| - 28 bytes.
+//
+// The scratchSpace should be set to a buffer at least 512 bytes. It's done this
+// way to avoid allocating stack space.
+//
+bool eicPresentationRetrieveEntryValue(
+ EicPresentation* ctx, const uint8_t* encryptedContent,
+ size_t encryptedContentSize, uint8_t* content, const char* nameSpace,
+ size_t nameSpaceLength, const char* name, size_t nameLength,
+ const uint8_t* accessControlProfileIds, size_t numAccessControlProfileIds,
+ uint8_t* scratchSpace, size_t scratchSpaceSize);
+
+// Returns the HMAC-SHA256 of |ToBeMaced| as per RFC 8051 "6.3. How to Compute
+// and Verify a MAC".
+bool eicPresentationFinishRetrieval(EicPresentation* ctx,
+ uint8_t* digestToBeMaced,
+ size_t* digestToBeMacedSize);
+
+// The data returned in |signatureOfToBeSigned| contains the ECDSA signature of
+// the ToBeSigned CBOR from RFC 8051 "4.4. Signing and Verification Process"
+// where content is set to the ProofOfDeletion CBOR.
+//
+bool eicPresentationDeleteCredential(
+ EicPresentation* ctx, const char* docType, size_t docTypeLength,
+ const uint8_t* challenge, size_t challengeSize, bool includeChallenge,
+ size_t proofOfDeletionCborSize,
+ uint8_t signatureOfToBeSigned[EIC_ECDSA_P256_SIGNATURE_SIZE]);
+
+// The data returned in |signatureOfToBeSigned| contains the ECDSA signature of
+// the ToBeSigned CBOR from RFC 8051 "4.4. Signing and Verification Process"
+// where content is set to the ProofOfOwnership CBOR.
+//
+bool eicPresentationProveOwnership(
+ EicPresentation* ctx, const char* docType, size_t docTypeLength,
+ bool testCredential, const uint8_t* challenge, size_t challengeSize,
+ size_t proofOfOwnershipCborSize,
+ uint8_t signatureOfToBeSigned[EIC_ECDSA_P256_SIGNATURE_SIZE]);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif // ANDROID_HARDWARE_IDENTITY_EIC_PRESENTATION_H
diff --git a/guest/hals/identity/libeic/EicProvisioning.c b/guest/hals/identity/libeic/EicProvisioning.c
new file mode 100644
index 0000000..1f691cd
--- /dev/null
+++ b/guest/hals/identity/libeic/EicProvisioning.c
@@ -0,0 +1,403 @@
+/*
+ * Copyright 2020, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "EicProvisioning.h"
+#include "EicCommon.h"
+
+bool eicProvisioningInit(EicProvisioning* ctx, bool testCredential) {
+ eicMemSet(ctx, '\0', sizeof(EicProvisioning));
+ ctx->testCredential = testCredential;
+ if (!eicOpsRandom(ctx->storageKey, EIC_AES_128_KEY_SIZE)) {
+ return false;
+ }
+
+ return true;
+}
+
+bool eicProvisioningInitForUpdate(EicProvisioning* ctx, bool testCredential,
+ const char* docType, size_t docTypeLength,
+ const uint8_t* encryptedCredentialKeys,
+ size_t encryptedCredentialKeysSize) {
+ uint8_t credentialKeys[EIC_CREDENTIAL_KEYS_CBOR_SIZE_FEATURE_VERSION_202101];
+
+ // For feature version 202009 it's 52 bytes long and for feature version
+ // 202101 it's 86 bytes (the additional data is the ProofOfProvisioning
+ // SHA-256). We need to support loading all feature versions.
+ //
+ bool expectPopSha256 = false;
+ if (encryptedCredentialKeysSize ==
+ EIC_CREDENTIAL_KEYS_CBOR_SIZE_FEATURE_VERSION_202009 + 28) {
+ /* do nothing */
+ } else if (encryptedCredentialKeysSize ==
+ EIC_CREDENTIAL_KEYS_CBOR_SIZE_FEATURE_VERSION_202101 + 28) {
+ expectPopSha256 = true;
+ } else {
+ eicDebug("Unexpected size %zd for encryptedCredentialKeys",
+ encryptedCredentialKeysSize);
+ return false;
+ }
+
+ eicMemSet(ctx, '\0', sizeof(EicProvisioning));
+ ctx->testCredential = testCredential;
+
+ if (!eicOpsDecryptAes128Gcm(
+ eicOpsGetHardwareBoundKey(testCredential), encryptedCredentialKeys,
+ encryptedCredentialKeysSize,
+ // DocType is the additionalAuthenticatedData
+ (const uint8_t*)docType, docTypeLength, credentialKeys)) {
+ eicDebug("Error decrypting CredentialKeys");
+ return false;
+ }
+
+ // It's supposed to look like this;
+ //
+ // Feature version 202009:
+ //
+ // CredentialKeys = [
+ // bstr, ; storageKey, a 128-bit AES key
+ // bstr, ; credentialPrivKey, the private key for credentialKey
+ // ]
+ //
+ // Feature version 202101:
+ //
+ // CredentialKeys = [
+ // bstr, ; storageKey, a 128-bit AES key
+ // bstr, ; credentialPrivKey, the private key for credentialKey
+ // bstr ; proofOfProvisioning SHA-256
+ // ]
+ //
+ // where storageKey is 16 bytes, credentialPrivateKey is 32 bytes, and
+ // proofOfProvisioning SHA-256 is 32 bytes.
+ //
+ if (credentialKeys[0] !=
+ (expectPopSha256 ? 0x83 : 0x82) || // array of two or three elements
+ credentialKeys[1] != 0x50 || // 16-byte bstr
+ credentialKeys[18] != 0x58 ||
+ credentialKeys[19] != 0x20) { // 32-byte bstr
+ eicDebug("Invalid CBOR for CredentialKeys");
+ return false;
+ }
+ if (expectPopSha256) {
+ if (credentialKeys[52] != 0x58 ||
+ credentialKeys[53] != 0x20) { // 32-byte bstr
+ eicDebug("Invalid CBOR for CredentialKeys");
+ return false;
+ }
+ }
+ eicMemCpy(ctx->storageKey, credentialKeys + 2, EIC_AES_128_KEY_SIZE);
+ eicMemCpy(ctx->credentialPrivateKey, credentialKeys + 20,
+ EIC_P256_PRIV_KEY_SIZE);
+ // Note: We don't care about the previous ProofOfProvisioning SHA-256
+ ctx->isUpdate = true;
+ return true;
+}
+
+bool eicProvisioningCreateCredentialKey(
+ EicProvisioning* ctx, const uint8_t* challenge, size_t challengeSize,
+ const uint8_t* applicationId, size_t applicationIdSize,
+ uint8_t* publicKeyCert, size_t* publicKeyCertSize) {
+ if (ctx->isUpdate) {
+ eicDebug("Cannot create CredentialKey on update");
+ return false;
+ }
+
+ if (!eicOpsCreateCredentialKey(ctx->credentialPrivateKey, challenge,
+ challengeSize, applicationId,
+ applicationIdSize, ctx->testCredential,
+ publicKeyCert, publicKeyCertSize)) {
+ return false;
+ }
+ return true;
+}
+
+bool eicProvisioningStartPersonalization(
+ EicProvisioning* ctx, int accessControlProfileCount, const int* entryCounts,
+ size_t numEntryCounts, const char* docType, size_t docTypeLength,
+ size_t expectedProofOfProvisioningSize) {
+ if (numEntryCounts >= EIC_MAX_NUM_NAMESPACES) {
+ return false;
+ }
+ if (accessControlProfileCount >= EIC_MAX_NUM_ACCESS_CONTROL_PROFILE_IDS) {
+ return false;
+ }
+
+ ctx->numEntryCounts = numEntryCounts;
+ if (numEntryCounts > EIC_MAX_NUM_NAMESPACES) {
+ return false;
+ }
+ for (size_t n = 0; n < numEntryCounts; n++) {
+ if (entryCounts[n] >= 256) {
+ return false;
+ }
+ ctx->entryCounts[n] = entryCounts[n];
+ }
+ ctx->curNamespace = -1;
+ ctx->curNamespaceNumProcessed = 0;
+
+ eicCborInit(&ctx->cbor, NULL, 0);
+
+ // What we're going to sign is the COSE ToBeSigned structure which
+ // looks like the following:
+ //
+ // Sig_structure = [
+ // context : "Signature" / "Signature1" / "CounterSignature",
+ // body_protected : empty_or_serialized_map,
+ // ? sign_protected : empty_or_serialized_map,
+ // external_aad : bstr,
+ // payload : bstr
+ // ]
+ //
+ eicCborAppendArray(&ctx->cbor, 4);
+ eicCborAppendStringZ(&ctx->cbor, "Signature1");
+
+ // The COSE Encoded protected headers is just a single field with
+ // COSE_LABEL_ALG (1) -> COSE_ALG_ECSDA_256 (-7). For simplicitly we just
+ // hard-code the CBOR encoding:
+ static const uint8_t coseEncodedProtectedHeaders[] = {0xa1, 0x01, 0x26};
+ eicCborAppendByteString(&ctx->cbor, coseEncodedProtectedHeaders,
+ sizeof(coseEncodedProtectedHeaders));
+
+ // We currently don't support Externally Supplied Data (RFC 8152 section 4.3)
+ // so external_aad is the empty bstr
+ static const uint8_t externalAad[0] = {};
+ eicCborAppendByteString(&ctx->cbor, externalAad, sizeof(externalAad));
+
+ // For the payload, the _encoded_ form follows here. We handle this by simply
+ // opening a bstr, and then writing the CBOR. This requires us to know the
+ // size of said bstr, ahead of time.
+ eicCborBegin(&ctx->cbor, EIC_CBOR_MAJOR_TYPE_BYTE_STRING,
+ expectedProofOfProvisioningSize);
+ ctx->expectedCborSizeAtEnd = expectedProofOfProvisioningSize + ctx->cbor.size;
+
+ eicOpsSha256Init(&ctx->proofOfProvisioningDigester);
+ eicCborEnableSecondaryDigesterSha256(&ctx->cbor,
+ &ctx->proofOfProvisioningDigester);
+
+ eicCborAppendArray(&ctx->cbor, 5);
+ eicCborAppendStringZ(&ctx->cbor, "ProofOfProvisioning");
+ eicCborAppendString(&ctx->cbor, docType, docTypeLength);
+
+ eicCborAppendArray(&ctx->cbor, accessControlProfileCount);
+
+ return true;
+}
+
+bool eicProvisioningAddAccessControlProfile(
+ EicProvisioning* ctx, int id, const uint8_t* readerCertificate,
+ size_t readerCertificateSize, bool userAuthenticationRequired,
+ uint64_t timeoutMillis, uint64_t secureUserId, uint8_t outMac[28],
+ uint8_t* scratchSpace, size_t scratchSpaceSize) {
+ EicCbor cborBuilder;
+ eicCborInit(&cborBuilder, scratchSpace, scratchSpaceSize);
+
+ if (!eicCborCalcAccessControl(
+ &cborBuilder, id, readerCertificate, readerCertificateSize,
+ userAuthenticationRequired, timeoutMillis, secureUserId)) {
+ return false;
+ }
+
+ // Calculate and return MAC
+ uint8_t nonce[12];
+ if (!eicOpsRandom(nonce, 12)) {
+ return false;
+ }
+ if (!eicOpsEncryptAes128Gcm(ctx->storageKey, nonce, NULL, 0,
+ cborBuilder.buffer, cborBuilder.size, outMac)) {
+ return false;
+ }
+
+ // The ACP CBOR in the provisioning receipt doesn't include secureUserId so
+ // build it again.
+ eicCborInit(&cborBuilder, scratchSpace, scratchSpaceSize);
+ if (!eicCborCalcAccessControl(
+ &cborBuilder, id, readerCertificate, readerCertificateSize,
+ userAuthenticationRequired, timeoutMillis, 0 /* secureUserId */)) {
+ return false;
+ }
+
+ // Append the CBOR from the local builder to the digester.
+ eicCborAppend(&ctx->cbor, cborBuilder.buffer, cborBuilder.size);
+
+ return true;
+}
+
+bool eicProvisioningBeginAddEntry(EicProvisioning* ctx,
+ const uint8_t* accessControlProfileIds,
+ size_t numAccessControlProfileIds,
+ const char* nameSpace, size_t nameSpaceLength,
+ const char* name, size_t nameLength,
+ uint64_t entrySize, uint8_t* scratchSpace,
+ size_t scratchSpaceSize) {
+ uint8_t* additionalDataCbor = scratchSpace;
+ const size_t additionalDataCborBufSize = scratchSpaceSize;
+ size_t additionalDataCborSize;
+
+ // We'll need to calc and store a digest of additionalData to check that it's
+ // the same additionalData being passed in for every
+ // eicProvisioningAddEntryValue() call...
+ if (!eicCborCalcEntryAdditionalData(
+ accessControlProfileIds, numAccessControlProfileIds, nameSpace,
+ nameSpaceLength, name, nameLength, additionalDataCbor,
+ additionalDataCborBufSize, &additionalDataCborSize,
+ ctx->additionalDataSha256)) {
+ return false;
+ }
+
+ if (ctx->curNamespace == -1) {
+ ctx->curNamespace = 0;
+ ctx->curNamespaceNumProcessed = 0;
+ // Opens the main map: { * Namespace => [ + Entry ] }
+ eicCborAppendMap(&ctx->cbor, ctx->numEntryCounts);
+ eicCborAppendString(&ctx->cbor, nameSpace, nameSpaceLength);
+ // Opens the per-namespace array: [ + Entry ]
+ eicCborAppendArray(&ctx->cbor, ctx->entryCounts[ctx->curNamespace]);
+ }
+
+ if (ctx->curNamespaceNumProcessed == ctx->entryCounts[ctx->curNamespace]) {
+ ctx->curNamespace += 1;
+ ctx->curNamespaceNumProcessed = 0;
+ eicCborAppendString(&ctx->cbor, nameSpace, nameSpaceLength);
+ // Opens the per-namespace array: [ + Entry ]
+ eicCborAppendArray(&ctx->cbor, ctx->entryCounts[ctx->curNamespace]);
+ }
+
+ eicCborAppendMap(&ctx->cbor, 3);
+ eicCborAppendStringZ(&ctx->cbor, "name");
+ eicCborAppendString(&ctx->cbor, name, nameLength);
+
+ ctx->curEntrySize = entrySize;
+ ctx->curEntryNumBytesReceived = 0;
+
+ eicCborAppendStringZ(&ctx->cbor, "value");
+
+ ctx->curNamespaceNumProcessed += 1;
+ return true;
+}
+
+bool eicProvisioningAddEntryValue(
+ EicProvisioning* ctx, const uint8_t* accessControlProfileIds,
+ size_t numAccessControlProfileIds, const char* nameSpace,
+ size_t nameSpaceLength, const char* name, size_t nameLength,
+ const uint8_t* content, size_t contentSize, uint8_t* outEncryptedContent,
+ uint8_t* scratchSpace, size_t scratchSpaceSize) {
+ uint8_t* additionalDataCbor = scratchSpace;
+ const size_t additionalDataCborBufSize = scratchSpaceSize;
+ size_t additionalDataCborSize;
+ uint8_t calculatedSha256[EIC_SHA256_DIGEST_SIZE];
+
+ if (!eicCborCalcEntryAdditionalData(
+ accessControlProfileIds, numAccessControlProfileIds, nameSpace,
+ nameSpaceLength, name, nameLength, additionalDataCbor,
+ additionalDataCborBufSize, &additionalDataCborSize,
+ calculatedSha256)) {
+ return false;
+ }
+ if (eicCryptoMemCmp(calculatedSha256, ctx->additionalDataSha256,
+ EIC_SHA256_DIGEST_SIZE) != 0) {
+ eicDebug("SHA-256 mismatch of additionalData");
+ return false;
+ }
+
+ eicCborAppend(&ctx->cbor, content, contentSize);
+
+ uint8_t nonce[12];
+ if (!eicOpsRandom(nonce, 12)) {
+ return false;
+ }
+ if (!eicOpsEncryptAes128Gcm(ctx->storageKey, nonce, content, contentSize,
+ additionalDataCbor, additionalDataCborSize,
+ outEncryptedContent)) {
+ return false;
+ }
+
+ // If done with this entry, close the map
+ ctx->curEntryNumBytesReceived += contentSize;
+ if (ctx->curEntryNumBytesReceived == ctx->curEntrySize) {
+ eicCborAppendStringZ(&ctx->cbor, "accessControlProfiles");
+ eicCborAppendArray(&ctx->cbor, numAccessControlProfileIds);
+ for (size_t n = 0; n < numAccessControlProfileIds; n++) {
+ eicCborAppendNumber(&ctx->cbor, accessControlProfileIds[n]);
+ }
+ }
+ return true;
+}
+
+bool eicProvisioningFinishAddingEntries(
+ EicProvisioning* ctx,
+ uint8_t signatureOfToBeSigned[EIC_ECDSA_P256_SIGNATURE_SIZE]) {
+ uint8_t cborSha256[EIC_SHA256_DIGEST_SIZE];
+
+ eicCborAppendBool(&ctx->cbor, ctx->testCredential);
+ eicCborFinal(&ctx->cbor, cborSha256);
+
+ // This verifies that the correct expectedProofOfProvisioningSize value was
+ // passed in at eicStartPersonalization() time.
+ if (ctx->cbor.size != ctx->expectedCborSizeAtEnd) {
+ eicDebug("CBOR size is %zd, was expecting %zd", ctx->cbor.size,
+ ctx->expectedCborSizeAtEnd);
+ return false;
+ }
+
+ if (!eicOpsEcDsa(ctx->credentialPrivateKey, cborSha256,
+ signatureOfToBeSigned)) {
+ eicDebug("Error signing proofOfProvisioning");
+ return false;
+ }
+
+ return true;
+}
+
+bool eicProvisioningFinishGetCredentialData(
+ EicProvisioning* ctx, const char* docType, size_t docTypeLength,
+ uint8_t* encryptedCredentialKeys, size_t* encryptedCredentialKeysSize) {
+ EicCbor cbor;
+ uint8_t cborBuf[86];
+
+ if (*encryptedCredentialKeysSize < 86 + 28) {
+ eicDebug("encryptedCredentialKeysSize is %zd which is insufficient");
+ return false;
+ }
+
+ eicCborInit(&cbor, cborBuf, sizeof(cborBuf));
+ eicCborAppendArray(&cbor, 3);
+ eicCborAppendByteString(&cbor, ctx->storageKey, EIC_AES_128_KEY_SIZE);
+ eicCborAppendByteString(&cbor, ctx->credentialPrivateKey,
+ EIC_P256_PRIV_KEY_SIZE);
+ uint8_t popSha256[EIC_SHA256_DIGEST_SIZE];
+ eicOpsSha256Final(&ctx->proofOfProvisioningDigester, popSha256);
+ eicCborAppendByteString(&cbor, popSha256, EIC_SHA256_DIGEST_SIZE);
+ if (cbor.size > sizeof(cborBuf)) {
+ eicDebug("Exceeded buffer size");
+ return false;
+ }
+
+ uint8_t nonce[12];
+ if (!eicOpsRandom(nonce, 12)) {
+ eicDebug("Error getting random");
+ return false;
+ }
+ if (!eicOpsEncryptAes128Gcm(eicOpsGetHardwareBoundKey(ctx->testCredential),
+ nonce, cborBuf, cbor.size,
+ // DocType is the additionalAuthenticatedData
+ (const uint8_t*)docType, docTypeLength,
+ encryptedCredentialKeys)) {
+ eicDebug("Error encrypting CredentialKeys");
+ return false;
+ }
+ *encryptedCredentialKeysSize = cbor.size + 28;
+
+ return true;
+}
diff --git a/guest/hals/identity/libeic/EicProvisioning.h b/guest/hals/identity/libeic/EicProvisioning.h
new file mode 100644
index 0000000..245a6e6
--- /dev/null
+++ b/guest/hals/identity/libeic/EicProvisioning.h
@@ -0,0 +1,144 @@
+/*
+ * Copyright 2020, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef ANDROID_HARDWARE_IDENTITY_EIC_PROVISIONING_H
+#define ANDROID_HARDWARE_IDENTITY_EIC_PROVISIONING_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include "EicCbor.h"
+
+#define EIC_MAX_NUM_NAMESPACES 32
+#define EIC_MAX_NUM_ACCESS_CONTROL_PROFILE_IDS 32
+
+typedef struct {
+ // Set by eicCreateCredentialKey() OR eicProvisioningInitForUpdate()
+ uint8_t credentialPrivateKey[EIC_P256_PRIV_KEY_SIZE];
+
+ int numEntryCounts;
+ uint8_t entryCounts[EIC_MAX_NUM_NAMESPACES];
+
+ int curNamespace;
+ int curNamespaceNumProcessed;
+
+ size_t curEntrySize;
+ size_t curEntryNumBytesReceived;
+
+ // Set by eicProvisioningInit() OR eicProvisioningInitForUpdate()
+ uint8_t storageKey[EIC_AES_128_KEY_SIZE];
+
+ size_t expectedCborSizeAtEnd;
+
+ // SHA-256 for AdditionalData, updated for each entry.
+ uint8_t additionalDataSha256[EIC_SHA256_DIGEST_SIZE];
+
+ // Digester just for ProofOfProvisioning (without Sig_structure).
+ EicSha256Ctx proofOfProvisioningDigester;
+
+ EicCbor cbor;
+
+ bool testCredential;
+
+ // Set to true if this is an update.
+ bool isUpdate;
+} EicProvisioning;
+
+bool eicProvisioningInit(EicProvisioning* ctx, bool testCredential);
+
+bool eicProvisioningInitForUpdate(EicProvisioning* ctx, bool testCredential,
+ const char* docType, size_t docTypeLength,
+ const uint8_t* encryptedCredentialKeys,
+ size_t encryptedCredentialKeysSize);
+
+bool eicProvisioningCreateCredentialKey(
+ EicProvisioning* ctx, const uint8_t* challenge, size_t challengeSize,
+ const uint8_t* applicationId, size_t applicationIdSize,
+ uint8_t* publicKeyCert, size_t* publicKeyCertSize);
+
+bool eicProvisioningStartPersonalization(
+ EicProvisioning* ctx, int accessControlProfileCount, const int* entryCounts,
+ size_t numEntryCounts, const char* docType, size_t docTypeLength,
+ size_t expectedProofOfProvisioningingSize);
+
+// The scratchSpace should be set to a buffer at least 512 bytes. It's done this
+// way to avoid allocating stack space.
+//
+bool eicProvisioningAddAccessControlProfile(
+ EicProvisioning* ctx, int id, const uint8_t* readerCertificate,
+ size_t readerCertificateSize, bool userAuthenticationRequired,
+ uint64_t timeoutMillis, uint64_t secureUserId, uint8_t outMac[28],
+ uint8_t* scratchSpace, size_t scratchSpaceSize);
+
+// The scratchSpace should be set to a buffer at least 512 bytes. It's done this
+// way to avoid allocating stack space.
+//
+bool eicProvisioningBeginAddEntry(EicProvisioning* ctx,
+ const uint8_t* accessControlProfileIds,
+ size_t numAccessControlProfileIds,
+ const char* nameSpace, size_t nameSpaceLength,
+ const char* name, size_t nameLength,
+ uint64_t entrySize, uint8_t* scratchSpace,
+ size_t scratchSpaceSize);
+
+// The outEncryptedContent array must be contentSize + 28 bytes long.
+//
+// The scratchSpace should be set to a buffer at least 512 bytes. It's done this
+// way to avoid allocating stack space.
+//
+bool eicProvisioningAddEntryValue(
+ EicProvisioning* ctx, const uint8_t* accessControlProfileIds,
+ size_t numAccessControlProfileIds, const char* nameSpace,
+ size_t nameSpaceLength, const char* name, size_t nameLength,
+ const uint8_t* content, size_t contentSize, uint8_t* outEncryptedContent,
+ uint8_t* scratchSpace, size_t scratchSpaceSize);
+
+// The data returned in |signatureOfToBeSigned| contains the ECDSA signature of
+// the ToBeSigned CBOR from RFC 8051 "4.4. Signing and Verification Process"
+// where content is set to the ProofOfProvisioninging CBOR.
+//
+bool eicProvisioningFinishAddingEntries(
+ EicProvisioning* ctx,
+ uint8_t signatureOfToBeSigned[EIC_ECDSA_P256_SIGNATURE_SIZE]);
+
+//
+//
+// The |encryptedCredentialKeys| array is set to AES-GCM-ENC(HBK, R,
+// CredentialKeys, docType) where
+//
+// CredentialKeys = [
+// bstr, ; storageKey, a 128-bit AES key
+// bstr ; credentialPrivKey, the private key for credentialKey
+// bstr ; SHA-256(ProofOfProvisioning)
+// ]
+//
+// for feature version 202101. For feature version 202009 the third field was
+// not present.
+//
+// Since |storageKey| is 16 bytes and |credentialPrivKey| is 32 bytes, the
+// encoded CBOR for CredentialKeys is 86 bytes and consequently
+// |encryptedCredentialKeys| will be no longer than 86 + 28 = 114 bytes.
+//
+bool eicProvisioningFinishGetCredentialData(
+ EicProvisioning* ctx, const char* docType, size_t docTypeLength,
+ uint8_t* encryptedCredentialKeys, size_t* encryptedCredentialKeysSize);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif // ANDROID_HARDWARE_IDENTITY_EIC_PROVISIONING_H
diff --git a/guest/hals/identity/libeic/libeic.h b/guest/hals/identity/libeic/libeic.h
new file mode 100644
index 0000000..8674ce3
--- /dev/null
+++ b/guest/hals/identity/libeic/libeic.h
@@ -0,0 +1,40 @@
+/*
+ * Copyright 2020, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef ANDROID_HARDWARE_IDENTITY_LIBEIC_H
+#define ANDROID_HARDWARE_IDENTITY_LIBEIC_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/* The EIC_INSIDE_LIBEIC_H preprocessor symbol is used to enforce
+ * library users to include only this file. All public interfaces, and
+ * only public interfaces, must be included here.
+ */
+#define EIC_INSIDE_LIBEIC_H
+#include "EicCbor.h"
+#include "EicCommon.h"
+#include "EicOps.h"
+#include "EicPresentation.h"
+#include "EicProvisioning.h"
+#undef EIC_INSIDE_LIBEIC_H
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif // ANDROID_HARDWARE_IDENTITY_LIBEIC_H
diff --git a/guest/hals/identity/service.cpp b/guest/hals/identity/service.cpp
new file mode 100644
index 0000000..b40dba4
--- /dev/null
+++ b/guest/hals/identity/service.cpp
@@ -0,0 +1,53 @@
+/*
+ * Copyright 2021, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#define LOG_TAG "android.hardware.identity-service"
+
+#include <android-base/logging.h>
+#include <android/binder_manager.h>
+#include <android/binder_process.h>
+
+#include "IdentityCredentialStore.h"
+
+#include "RemoteSecureHardwareProxy.h"
+
+using ::android::sp;
+using ::android::base::InitLogging;
+using ::android::base::StderrLogger;
+
+using ::aidl::android::hardware::identity::IdentityCredentialStore;
+using ::android::hardware::identity::RemoteSecureHardwareProxyFactory;
+using ::android::hardware::identity::SecureHardwareProxyFactory;
+
+int main(int /*argc*/, char* argv[]) {
+ InitLogging(argv, StderrLogger);
+
+ sp<SecureHardwareProxyFactory> hwProxyFactory =
+ new RemoteSecureHardwareProxyFactory();
+
+ ABinderProcess_setThreadPoolMaxThreadCount(0);
+ std::shared_ptr<IdentityCredentialStore> store =
+ ndk::SharedRefBase::make<IdentityCredentialStore>(hwProxyFactory);
+
+ const std::string instance =
+ std::string(IdentityCredentialStore::descriptor) + "/default";
+ binder_status_t status =
+ AServiceManager_addService(store->asBinder().get(), instance.c_str());
+ CHECK_EQ(status, STATUS_OK);
+
+ ABinderProcess_joinThreadPool();
+ return EXIT_FAILURE; // should not reach
+}
diff --git a/guest/hals/keymaster/remote/remote_keymaster4_device.cpp b/guest/hals/keymaster/remote/remote_keymaster4_device.cpp
index b82aa83..dd0cd05 100644
--- a/guest/hals/keymaster/remote/remote_keymaster4_device.cpp
+++ b/guest/hals/keymaster/remote/remote_keymaster4_device.cpp
@@ -20,9 +20,10 @@
#include "remote_keymaster4_device.h"
#include <android/hardware/keymaster/3.0/IKeymasterDevice.h>
-#include <authorization_set.h>
#include <cutils/log.h>
#include <keymaster/android_keymaster_messages.h>
+#include <keymaster/authorization_set.h>
+#include <keymaster_tags.h>
using ::keymaster::AbortOperationRequest;
using ::keymaster::AbortOperationResponse;
diff --git a/guest/hals/keymint/remote/Android.bp b/guest/hals/keymint/remote/Android.bp
index a383c45..4944d37 100644
--- a/guest/hals/keymint/remote/Android.bp
+++ b/guest/hals/keymint/remote/Android.bp
@@ -32,9 +32,8 @@
"-Wextra",
],
shared_libs: [
- "android.hardware.security.keymint-V1-ndk_platform",
- "android.hardware.security.secureclock-V1-ndk_platform",
- "android.hardware.security.sharedsecret-V1-ndk_platform",
+ "android.hardware.security.secureclock-V1-ndk",
+ "android.hardware.security.sharedsecret-V1-ndk",
"lib_android_keymaster_keymint_utils",
"libbase",
"libbinder_ndk",
@@ -52,11 +51,24 @@
"remote_keymint_device.cpp",
"remote_keymint_operation.cpp",
"remote_keymaster.cpp",
+ "remote_remotely_provisioned_component.cpp",
"remote_secure_clock.cpp",
"remote_shared_secret.cpp",
"service.cpp",
],
defaults: [
"cuttlefish_guest_only",
+ "keymint_use_latest_hal_aidl_ndk_shared",
],
+ required: [
+ "RemoteProvisioner",
+ "android.hardware.hardware_keystore.remote-keymint.xml",
+ ],
+}
+
+prebuilt_etc {
+ name: "android.hardware.hardware_keystore.remote-keymint.xml",
+ sub_dir: "permissions",
+ vendor: true,
+ src: "android.hardware.hardware_keystore.remote-keymint.xml",
}
diff --git a/guest/hals/keymint/remote/android.hardware.hardware_keystore.remote-keymint.xml b/guest/hals/keymint/remote/android.hardware.hardware_keystore.remote-keymint.xml
new file mode 100644
index 0000000..2ebf1fe
--- /dev/null
+++ b/guest/hals/keymint/remote/android.hardware.hardware_keystore.remote-keymint.xml
@@ -0,0 +1,18 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright 2021 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<permissions>
+ <feature name="android.hardware.hardware_keystore" version="200" />
+</permissions>
diff --git a/guest/hals/keymint/remote/android.hardware.security.keymint-service.remote.xml b/guest/hals/keymint/remote/android.hardware.security.keymint-service.remote.xml
index 4aa05ef..a4d0302 100644
--- a/guest/hals/keymint/remote/android.hardware.security.keymint-service.remote.xml
+++ b/guest/hals/keymint/remote/android.hardware.security.keymint-service.remote.xml
@@ -1,10 +1,12 @@
<manifest version="1.0" type="device">
<hal format="aidl">
<name>android.hardware.security.keymint</name>
+ <version>2</version>
<fqname>IKeyMintDevice/default</fqname>
</hal>
<hal format="aidl">
<name>android.hardware.security.keymint</name>
+ <version>2</version>
<fqname>IRemotelyProvisionedComponent/default</fqname>
</hal>
</manifest>
diff --git a/guest/hals/keymint/remote/remote_keymaster.cpp b/guest/hals/keymint/remote/remote_keymaster.cpp
index 5eee143..763c139 100644
--- a/guest/hals/keymint/remote/remote_keymaster.cpp
+++ b/guest/hals/keymint/remote/remote_keymaster.cpp
@@ -17,13 +17,15 @@
#include "remote_keymaster.h"
#include <android-base/logging.h>
+#include <android-base/properties.h>
+#include <android-base/strings.h>
#include <keymaster/android_keymaster_messages.h>
#include <keymaster/keymaster_configuration.h>
namespace keymaster {
RemoteKeymaster::RemoteKeymaster(cuttlefish::KeymasterChannel* channel,
- uint32_t message_version)
+ int32_t message_version)
: channel_(channel), message_version_(message_version) {}
RemoteKeymaster::~RemoteKeymaster() {}
@@ -67,6 +69,48 @@
return false;
}
+ // Set the vendor patchlevel to value retrieved from system property (which
+ // requires SELinux permission).
+ ConfigureVendorPatchlevelRequest vendor_req(message_version());
+ vendor_req.vendor_patchlevel = GetVendorPatchlevel();
+ ConfigureVendorPatchlevelResponse vendor_rsp =
+ ConfigureVendorPatchlevel(vendor_req);
+ if (vendor_rsp.error != KM_ERROR_OK) {
+ LOG(ERROR) << "Failed to configure keymaster vendor patchlevel: "
+ << vendor_rsp.error;
+ return false;
+ }
+
+ // Set the boot patchlevel to value retrieved from system property (which
+ // requires SELinux permission).
+ ConfigureBootPatchlevelRequest boot_req(message_version());
+ static constexpr char boot_prop_name[] = "ro.vendor.boot_security_patch";
+ auto boot_prop_value = android::base::GetProperty(boot_prop_name, "");
+ boot_prop_value = android::base::StringReplace(boot_prop_value.data(), "-",
+ "", /* all */ true);
+ boot_req.boot_patchlevel = std::stoi(boot_prop_value);
+ ConfigureBootPatchlevelResponse boot_rsp = ConfigureBootPatchlevel(boot_req);
+ if (boot_rsp.error != KM_ERROR_OK) {
+ LOG(ERROR) << "Failed to configure keymaster boot patchlevel: "
+ << boot_rsp.error;
+ return false;
+ }
+
+ // Pass verified boot information to the remote KM implementation
+ auto vbmeta_digest = GetVbmetaDigest();
+ if (vbmeta_digest) {
+ ConfigureVerifiedBootInfoRequest request(
+ message_version(), GetVerifiedBootState(), GetBootloaderState(),
+ *vbmeta_digest);
+ ConfigureVerifiedBootInfoResponse response =
+ ConfigureVerifiedBootInfo(request);
+ if (response.error != KM_ERROR_OK) {
+ LOG(ERROR) << "Failed to configure keymaster verified boot info: "
+ << response.error;
+ return false;
+ }
+ }
+
return true;
}
@@ -122,15 +166,25 @@
void RemoteKeymaster::GenerateKey(const GenerateKeyRequest& request,
GenerateKeyResponse* response) {
- GenerateKeyRequest datedRequest(request.message_version);
- datedRequest.key_description = request.key_description;
-
- if (!request.key_description.Contains(TAG_CREATION_DATETIME)) {
+ if (message_version_ < MessageVersion(KmVersion::KEYMINT_1) &&
+ !request.key_description.Contains(TAG_CREATION_DATETIME)) {
+ GenerateKeyRequest datedRequest(request.message_version);
datedRequest.key_description.push_back(TAG_CREATION_DATETIME,
java_time(time(NULL)));
+ ForwardCommand(GENERATE_KEY, datedRequest, response);
+ } else {
+ ForwardCommand(GENERATE_KEY, request, response);
}
+}
- ForwardCommand(GENERATE_KEY, datedRequest, response);
+void RemoteKeymaster::GenerateRkpKey(const GenerateRkpKeyRequest& request,
+ GenerateRkpKeyResponse* response) {
+ ForwardCommand(GENERATE_RKP_KEY, request, response);
+}
+
+void RemoteKeymaster::GenerateCsr(const GenerateCsrRequest& request,
+ GenerateCsrResponse* response) {
+ ForwardCommand(GENERATE_CSR, request, response);
}
void RemoteKeymaster::GetKeyCharacteristics(
@@ -234,8 +288,35 @@
void RemoteKeymaster::GenerateTimestampToken(
GenerateTimestampTokenRequest& request,
GenerateTimestampTokenResponse* response) {
- // TODO(aosp/1641315): Send a message to the host.
ForwardCommand(GENERATE_TIMESTAMP_TOKEN, request, response);
}
+ConfigureVendorPatchlevelResponse RemoteKeymaster::ConfigureVendorPatchlevel(
+ const ConfigureVendorPatchlevelRequest& request) {
+ ConfigureVendorPatchlevelResponse response(message_version());
+ ForwardCommand(CONFIGURE_VENDOR_PATCHLEVEL, request, &response);
+ return response;
+}
+
+ConfigureBootPatchlevelResponse RemoteKeymaster::ConfigureBootPatchlevel(
+ const ConfigureBootPatchlevelRequest& request) {
+ ConfigureBootPatchlevelResponse response(message_version());
+ ForwardCommand(CONFIGURE_BOOT_PATCHLEVEL, request, &response);
+ return response;
+}
+
+ConfigureVerifiedBootInfoResponse RemoteKeymaster::ConfigureVerifiedBootInfo(
+ const ConfigureVerifiedBootInfoRequest& request) {
+ ConfigureVerifiedBootInfoResponse response(message_version());
+ ForwardCommand(CONFIGURE_VERIFIED_BOOT_INFO, request, &response);
+ return response;
+}
+
+GetRootOfTrustResponse RemoteKeymaster::GetRootOfTrust(
+ const GetRootOfTrustRequest& request) {
+ GetRootOfTrustResponse response(message_version());
+ ForwardCommand(GET_ROOT_OF_TRUST, request, &response);
+ return response;
+}
+
} // namespace keymaster
diff --git a/guest/hals/keymint/remote/remote_keymaster.h b/guest/hals/keymint/remote/remote_keymaster.h
index bd86012..2e0668f 100644
--- a/guest/hals/keymint/remote/remote_keymaster.h
+++ b/guest/hals/keymint/remote/remote_keymaster.h
@@ -26,14 +26,14 @@
class RemoteKeymaster {
private:
cuttlefish::KeymasterChannel* channel_;
- const uint32_t message_version_;
+ const int32_t message_version_;
void ForwardCommand(AndroidKeymasterCommand command, const Serializable& req,
KeymasterResponse* rsp);
public:
RemoteKeymaster(cuttlefish::KeymasterChannel*,
- uint32_t message_version = kDefaultMessageVersion);
+ int32_t message_version = kDefaultMessageVersion);
~RemoteKeymaster();
bool Initialize();
void GetVersion(const GetVersionRequest& request,
@@ -55,6 +55,10 @@
void Configure(const ConfigureRequest& request, ConfigureResponse* response);
void GenerateKey(const GenerateKeyRequest& request,
GenerateKeyResponse* response);
+ void GenerateRkpKey(const GenerateRkpKeyRequest& request,
+ GenerateRkpKeyResponse* response);
+ void GenerateCsr(const GenerateCsrRequest& request,
+ GenerateCsrResponse* response);
void GetKeyCharacteristics(const GetKeyCharacteristicsRequest& request,
GetKeyCharacteristicsResponse* response);
void ImportKey(const ImportKeyRequest& request, ImportKeyResponse* response);
@@ -82,12 +86,19 @@
const VerifyAuthorizationRequest& request);
DeviceLockedResponse DeviceLocked(const DeviceLockedRequest& request);
EarlyBootEndedResponse EarlyBootEnded();
+ ConfigureVendorPatchlevelResponse ConfigureVendorPatchlevel(
+ const ConfigureVendorPatchlevelRequest& request);
+ ConfigureBootPatchlevelResponse ConfigureBootPatchlevel(
+ const ConfigureBootPatchlevelRequest& request);
+ ConfigureVerifiedBootInfoResponse ConfigureVerifiedBootInfo(
+ const ConfigureVerifiedBootInfoRequest& request);
void GenerateTimestampToken(GenerateTimestampTokenRequest& request,
GenerateTimestampTokenResponse* response);
+ GetRootOfTrustResponse GetRootOfTrust(const GetRootOfTrustRequest& request);
// CF HAL and remote sides are always compiled together, so will never
// disagree about message versions.
- uint32_t message_version() { return message_version_; }
+ int32_t message_version() { return message_version_; }
};
} // namespace keymaster
diff --git a/guest/hals/keymint/remote/remote_keymint_device.cpp b/guest/hals/keymint/remote/remote_keymint_device.cpp
index 55903b7..c6db283 100644
--- a/guest/hals/keymint/remote/remote_keymint_device.cpp
+++ b/guest/hals/keymint/remote/remote_keymint_device.cpp
@@ -37,17 +37,19 @@
namespace {
vector<KeyCharacteristics> convertKeyCharacteristics(
- SecurityLevel keyMintSecurityLevel, const AuthorizationSet& sw_enforced,
- const AuthorizationSet& hw_enforced) {
+ const vector<KeyParameter>& keyParams, SecurityLevel keyMintSecurityLevel,
+ const AuthorizationSet& sw_enforced, const AuthorizationSet& hw_enforced,
+ bool include_keystore_enforced = true) {
KeyCharacteristics keyMintEnforced{keyMintSecurityLevel, {}};
if (keyMintSecurityLevel != SecurityLevel::SOFTWARE) {
// We're pretending to be TRUSTED_ENVIRONMENT or STRONGBOX.
keyMintEnforced.authorizations = kmParamSet2Aidl(hw_enforced);
- // Put all the software authorizations in the keystore list.
- KeyCharacteristics keystoreEnforced{SecurityLevel::KEYSTORE,
- kmParamSet2Aidl(sw_enforced)};
- return {std::move(keyMintEnforced), std::move(keystoreEnforced)};
+ if (include_keystore_enforced && !sw_enforced.empty()) {
+ return {std::move(keyMintEnforced),
+ {SecurityLevel::KEYSTORE, kmParamSet2Aidl(sw_enforced)}};
+ }
+ return {std::move(keyMintEnforced)};
}
KeyCharacteristics keystoreEnforced{SecurityLevel::KEYSTORE, {}};
@@ -77,6 +79,11 @@
/* Unenforceable */
case KM_TAG_CREATION_DATETIME:
+ for (const auto& p : keyParams) {
+ if (p.tag == Tag::CREATION_DATETIME) {
+ keystoreEnforced.authorizations.push_back(kmParam2Aidl(entry));
+ }
+ }
break;
/* Disallowed in KeyCharacteristics */
@@ -160,10 +167,12 @@
vector<KeyCharacteristics> retval;
retval.reserve(2);
- if (!keyMintEnforced.authorizations.empty())
+ if (!keyMintEnforced.authorizations.empty()) {
retval.push_back(std::move(keyMintEnforced));
- if (!keystoreEnforced.authorizations.empty())
+ }
+ if (include_keystore_enforced && !keystoreEnforced.authorizations.empty()) {
retval.push_back(std::move(keystoreEnforced));
+ }
return retval;
}
@@ -245,7 +254,7 @@
creationResult->keyBlob = kmBlob2vector(response.key_blob);
creationResult->keyCharacteristics = convertKeyCharacteristics(
- securityLevel_, response.unenforced, response.enforced);
+ keyParams, securityLevel_, response.unenforced, response.enforced);
creationResult->certificateChain =
convertCertificateChain(response.certificate_chain);
return ScopedAStatus::ok();
@@ -279,7 +288,7 @@
creationResult->keyBlob = kmBlob2vector(response.key_blob);
creationResult->keyCharacteristics = convertKeyCharacteristics(
- securityLevel_, response.unenforced, response.enforced);
+ keyParams, securityLevel_, response.unenforced, response.enforced);
creationResult->certificateChain =
convertCertificateChain(response.certificate_chain);
@@ -310,7 +319,7 @@
creationResult->keyBlob = kmBlob2vector(response.key_blob);
creationResult->keyCharacteristics = convertKeyCharacteristics(
- securityLevel_, response.unenforced, response.enforced);
+ unwrappingParams, securityLevel_, response.unenforced, response.enforced);
creationResult->certificateChain =
convertCertificateChain(response.certificate_chain);
@@ -413,10 +422,52 @@
}
ScopedAStatus RemoteKeyMintDevice::getKeyCharacteristics(
- const std::vector<uint8_t>& /* storageKeyBlob */,
- const std::vector<uint8_t>& /* appId */,
- const std::vector<uint8_t>& /* appData */,
- std::vector<KeyCharacteristics>* /* keyCharacteristics */) {
+ const std::vector<uint8_t>& storageKeyBlob,
+ const std::vector<uint8_t>& appId, const std::vector<uint8_t>& appData,
+ std::vector<KeyCharacteristics>* keyCharacteristics) {
+ GetKeyCharacteristicsRequest request(impl_.message_version());
+ request.SetKeyMaterial(storageKeyBlob.data(), storageKeyBlob.size());
+ addClientAndAppData(appId, appData, &request.additional_params);
+
+ GetKeyCharacteristicsResponse response(impl_.message_version());
+ impl_.GetKeyCharacteristics(request, &response);
+
+ if (response.error != KM_ERROR_OK) {
+ return kmError2ScopedAStatus(response.error);
+ }
+
+ *keyCharacteristics = convertKeyCharacteristics(
+ {} /*keyParams*/, securityLevel_, response.unenforced, response.enforced,
+ false /*include_keystore_enforced*/);
+
+ return ScopedAStatus::ok();
+}
+
+ScopedAStatus RemoteKeyMintDevice::getRootOfTrustChallenge(
+ std::array<uint8_t, 16>* /* challenge */) {
return kmError2ScopedAStatus(KM_ERROR_UNIMPLEMENTED);
}
+
+ScopedAStatus RemoteKeyMintDevice::getRootOfTrust(
+ const std::array<uint8_t, 16>& challenge,
+ std::vector<uint8_t>* rootOfTrust) {
+ if (!rootOfTrust) {
+ return kmError2ScopedAStatus(KM_ERROR_UNEXPECTED_NULL_POINTER);
+ }
+ GetRootOfTrustRequest request(impl_.message_version(),
+ {challenge.begin(), challenge.end()});
+ GetRootOfTrustResponse response = impl_.GetRootOfTrust(request);
+ if (response.error != KM_ERROR_OK) {
+ return kmError2ScopedAStatus(response.error);
+ }
+
+ *rootOfTrust = std::move(response.rootOfTrust);
+ return ScopedAStatus::ok();
+}
+
+ScopedAStatus RemoteKeyMintDevice::sendRootOfTrust(
+ const std::vector<uint8_t>& /* rootOfTrust */) {
+ return kmError2ScopedAStatus(KM_ERROR_UNIMPLEMENTED);
+}
+
} // namespace aidl::android::hardware::security::keymint
diff --git a/guest/hals/keymint/remote/remote_keymint_device.h b/guest/hals/keymint/remote/remote_keymint_device.h
index c3ebad1..a7a8293 100644
--- a/guest/hals/keymint/remote/remote_keymint_device.h
+++ b/guest/hals/keymint/remote/remote_keymint_device.h
@@ -82,6 +82,13 @@
const std::vector<uint8_t>& appId, const std::vector<uint8_t>& appData,
std::vector<KeyCharacteristics>* keyCharacteristics) override;
+ ScopedAStatus getRootOfTrustChallenge(
+ std::array<uint8_t, 16>* challenge) override;
+ ScopedAStatus getRootOfTrust(const std::array<uint8_t, 16>& challenge,
+ std::vector<uint8_t>* rootOfTrust) override;
+ ScopedAStatus sendRootOfTrust(
+ const std::vector<uint8_t>& rootOfTrust) override;
+
protected:
::keymaster::RemoteKeymaster& impl_;
SecurityLevel securityLevel_;
diff --git a/guest/hals/keymint/remote/remote_remotely_provisioned_component.cpp b/guest/hals/keymint/remote/remote_remotely_provisioned_component.cpp
new file mode 100644
index 0000000..4098362
--- /dev/null
+++ b/guest/hals/keymint/remote/remote_remotely_provisioned_component.cpp
@@ -0,0 +1,109 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <aidl/android/hardware/security/keymint/RpcHardwareInfo.h>
+#include <cppbor.h>
+#include <cppbor_parse.h>
+#include <keymaster/cppcose/cppcose.h>
+#include <keymaster/keymaster_configuration.h>
+#include <openssl/bn.h>
+#include <openssl/ec.h>
+#include <openssl/rand.h>
+#include <openssl/x509.h>
+
+#include <variant>
+
+#include "KeyMintUtils.h"
+#include "android/binder_auto_utils.h"
+#include "remote_remotely_provisioned_component.h"
+
+namespace aidl::android::hardware::security::keymint {
+namespace {
+using namespace cppcose;
+using namespace keymaster;
+
+using ::aidl::android::hardware::security::keymint::km_utils::kmBlob2vector;
+using ::ndk::ScopedAStatus;
+
+// Error codes from the provisioning stack are negated.
+ndk::ScopedAStatus toKeymasterError(const KeymasterResponse& response) {
+ auto error =
+ static_cast<keymaster_error_t>(-static_cast<int32_t>(response.error));
+ return ::aidl::android::hardware::security::keymint::km_utils::
+ kmError2ScopedAStatus(error);
+}
+
+} // namespace
+
+RemoteRemotelyProvisionedComponent::RemoteRemotelyProvisionedComponent(
+ keymaster::RemoteKeymaster& impl)
+ : impl_(impl) {}
+
+ScopedAStatus RemoteRemotelyProvisionedComponent::getHardwareInfo(
+ RpcHardwareInfo* info) {
+ info->versionNumber = 2;
+ info->rpcAuthorName = "Google";
+ info->supportedEekCurve = RpcHardwareInfo::CURVE_25519;
+ info->uniqueId = "remote keymint";
+ return ScopedAStatus::ok();
+}
+
+ScopedAStatus RemoteRemotelyProvisionedComponent::generateEcdsaP256KeyPair(
+ bool testMode, MacedPublicKey* macedPublicKey,
+ std::vector<uint8_t>* privateKeyHandle) {
+ GenerateRkpKeyRequest request(impl_.message_version());
+ request.test_mode = testMode;
+ GenerateRkpKeyResponse response(impl_.message_version());
+ impl_.GenerateRkpKey(request, &response);
+ if (response.error != KM_ERROR_OK) {
+ return toKeymasterError(response);
+ }
+
+ macedPublicKey->macedKey = km_utils::kmBlob2vector(response.maced_public_key);
+ *privateKeyHandle = km_utils::kmBlob2vector(response.key_blob);
+ return ScopedAStatus::ok();
+}
+
+ScopedAStatus RemoteRemotelyProvisionedComponent::generateCertificateRequest(
+ bool testMode, const std::vector<MacedPublicKey>& keysToSign,
+ const std::vector<uint8_t>& endpointEncCertChain,
+ const std::vector<uint8_t>& challenge, DeviceInfo* deviceInfo,
+ ProtectedData* protectedData, std::vector<uint8_t>* keysToSignMac) {
+ GenerateCsrRequest request(impl_.message_version());
+ request.test_mode = testMode;
+ request.num_keys = keysToSign.size();
+ request.keys_to_sign_array = new KeymasterBlob[keysToSign.size()];
+ for (size_t i = 0; i < keysToSign.size(); i++) {
+ request.SetKeyToSign(i, keysToSign[i].macedKey.data(),
+ keysToSign[i].macedKey.size());
+ }
+ request.SetEndpointEncCertChain(endpointEncCertChain.data(),
+ endpointEncCertChain.size());
+ request.SetChallenge(challenge.data(), challenge.size());
+ GenerateCsrResponse response(impl_.message_version());
+ impl_.GenerateCsr(request, &response);
+
+ if (response.error != KM_ERROR_OK) {
+ return toKeymasterError(response);
+ }
+ deviceInfo->deviceInfo = km_utils::kmBlob2vector(response.device_info_blob);
+ protectedData->protectedData =
+ km_utils::kmBlob2vector(response.protected_data_blob);
+ *keysToSignMac = km_utils::kmBlob2vector(response.keys_to_sign_mac);
+ return ScopedAStatus::ok();
+}
+
+} // namespace aidl::android::hardware::security::keymint
diff --git a/guest/hals/keymint/remote/remote_remotely_provisioned_component.h b/guest/hals/keymint/remote/remote_remotely_provisioned_component.h
new file mode 100644
index 0000000..35e182a
--- /dev/null
+++ b/guest/hals/keymint/remote/remote_remotely_provisioned_component.h
@@ -0,0 +1,54 @@
+/*
+ * Copyright 2021, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <cstdint>
+#include <vector>
+
+#include <aidl/android/hardware/security/keymint/BnRemotelyProvisionedComponent.h>
+#include <aidl/android/hardware/security/keymint/MacedPublicKey.h>
+#include <aidl/android/hardware/security/keymint/RpcHardwareInfo.h>
+#include <aidl/android/hardware/security/keymint/SecurityLevel.h>
+
+#include "KeyMintUtils.h"
+#include "guest/hals/keymint/remote/remote_keymaster.h"
+
+namespace aidl::android::hardware::security::keymint {
+
+class RemoteRemotelyProvisionedComponent
+ : public BnRemotelyProvisionedComponent {
+ public:
+ explicit RemoteRemotelyProvisionedComponent(keymaster::RemoteKeymaster& impl);
+
+ ndk::ScopedAStatus getHardwareInfo(RpcHardwareInfo* info) override;
+
+ ndk::ScopedAStatus generateEcdsaP256KeyPair(
+ bool testMode, MacedPublicKey* macedPublicKey,
+ std::vector<uint8_t>* privateKeyHandle) override;
+
+ ndk::ScopedAStatus generateCertificateRequest(
+ bool testMode, const std::vector<MacedPublicKey>& keysToSign,
+ const std::vector<uint8_t>& endpointEncCertChain,
+ const std::vector<uint8_t>& challenge, DeviceInfo* deviceInfo,
+ ProtectedData* protectedData,
+ std::vector<uint8_t>* keysToSignMac) override;
+
+ private:
+ keymaster::RemoteKeymaster& impl_;
+};
+
+} // namespace aidl::android::hardware::security::keymint
diff --git a/guest/hals/keymint/remote/service.cpp b/guest/hals/keymint/remote/service.cpp
index 404df77..1fb0143 100644
--- a/guest/hals/keymint/remote/service.cpp
+++ b/guest/hals/keymint/remote/service.cpp
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-#define LOG_TAG "android.hardware.security.keymint-service"
+#define LOG_TAG "android.hardware.security.keymint-service.remote"
#include <android-base/logging.h>
#include <android/binder_manager.h>
@@ -23,24 +23,31 @@
#include <keymaster/android_keymaster_messages.h>
#include <keymaster/km_version.h>
#include <keymaster/soft_keymaster_logger.h>
-#include "guest/hals/keymint/remote/remote_keymint_device.h"
+#include <aidl/android/hardware/security/keymint/SecurityLevel.h>
#include <guest/hals/keymint/remote/remote_keymaster.h>
#include <guest/hals/keymint/remote/remote_keymint_device.h>
+#include <guest/hals/keymint/remote/remote_remotely_provisioned_component.h>
#include <guest/hals/keymint/remote/remote_secure_clock.h>
#include <guest/hals/keymint/remote/remote_shared_secret.h>
#include "common/libs/fs/shared_fd.h"
#include "common/libs/security/keymaster_channel.h"
-static const char device[] = "/dev/hvc3";
+namespace {
+
+const char device[] = "/dev/hvc3";
using aidl::android::hardware::security::keymint::RemoteKeyMintDevice;
+using aidl::android::hardware::security::keymint::
+ RemoteRemotelyProvisionedComponent;
using aidl::android::hardware::security::keymint::SecurityLevel;
using aidl::android::hardware::security::secureclock::RemoteSecureClock;
using aidl::android::hardware::security::sharedsecret::RemoteSharedSecret;
+using keymaster::GenerateTimestampTokenRequest;
+using keymaster::GenerateTimestampTokenResponse;
template <typename T, class... Args>
-static std::shared_ptr<T> addService(Args&&... args) {
+std::shared_ptr<T> addService(Args&&... args) {
std::shared_ptr<T> ser =
ndk::SharedRefBase::make<T>(std::forward<Args>(args)...);
auto instanceName = std::string(T::descriptor) + "/default";
@@ -51,6 +58,21 @@
return ser;
}
+SecurityLevel getSecurityLevel(::keymaster::RemoteKeymaster& remote_keymaster) {
+ GenerateTimestampTokenRequest request(remote_keymaster.message_version());
+ GenerateTimestampTokenResponse response(remote_keymaster.message_version());
+ remote_keymaster.GenerateTimestampToken(request, &response);
+
+ if (response.error != STATUS_OK) {
+ LOG(FATAL) << "Error getting timestamp token from remote keymaster: "
+ << response.error;
+ }
+
+ return static_cast<SecurityLevel>(response.token.security_level);
+}
+
+} // namespace
+
int main(int, char** argv) {
android::base::InitLogging(argv, android::base::KernelLogger);
// Zero threads seems like a useless pool, but below we'll join this thread to
@@ -71,12 +93,17 @@
keymaster::RemoteKeymaster remote_keymaster(
&keymasterChannel, keymaster::MessageVersion(
- keymaster::KmVersion::KEYMINT_1, 0 /* km_date */));
+ keymaster::KmVersion::KEYMINT_2, 0 /* km_date */));
+
+ if (!remote_keymaster.Initialize()) {
+ LOG(FATAL) << "Could not initialize keymaster";
+ }
addService<RemoteKeyMintDevice>(remote_keymaster,
- SecurityLevel::TRUSTED_ENVIRONMENT);
+ getSecurityLevel(remote_keymaster));
addService<RemoteSecureClock>(remote_keymaster);
addService<RemoteSharedSecret>(remote_keymaster);
+ addService<RemoteRemotelyProvisionedComponent>(remote_keymaster);
ABinderProcess_joinThreadPool();
return EXIT_FAILURE; // should not reach
diff --git a/guest/hals/nfc/Android.bp b/guest/hals/nfc/Android.bp
new file mode 100644
index 0000000..bf71656
--- /dev/null
+++ b/guest/hals/nfc/Android.bp
@@ -0,0 +1,30 @@
+package {
+ default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+cc_binary {
+ name: "android.hardware.nfc-service.cuttlefish",
+ relative_install_path: "hw",
+ init_rc: ["nfc-service-cuttlefish.rc"],
+ vintf_fragments: ["nfc-service-cuttlefish.xml"],
+ vendor: true,
+ cflags: [
+ "-Wall",
+ "-Wextra",
+ ],
+ shared_libs: [
+ "libbase",
+ "liblog",
+ "libutils",
+ "libbinder_ndk",
+ "android.hardware.nfc-V1-ndk",
+ ],
+ required: [
+ "libnfc-hal-cf.conf-default",
+ ],
+ srcs: [
+ "main.cc",
+ "Nfc.cc",
+ "Cf_hal_api.cc",
+ ],
+}
diff --git a/guest/hals/nfc/Cf_hal_api.cc b/guest/hals/nfc/Cf_hal_api.cc
new file mode 100644
index 0000000..ed564d2
--- /dev/null
+++ b/guest/hals/nfc/Cf_hal_api.cc
@@ -0,0 +1,346 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <android-base/logging.h>
+#include <android-base/properties.h>
+#include <android-base/stringprintf.h>
+#include <dlfcn.h>
+#include <errno.h>
+#include <pthread.h>
+#include <string.h>
+
+#include "Cf_hal_api.h"
+#include "hardware_nfc.h"
+
+using android::base::StringPrintf;
+
+bool hal_opened = false;
+bool dbg_logging = false;
+pthread_mutex_t hmutex = PTHREAD_MUTEX_INITIALIZER;
+nfc_stack_callback_t* e_cback;
+nfc_stack_data_callback_t* d_cback;
+
+static struct aidl_callback_struct {
+ pthread_mutex_t mutex;
+ pthread_cond_t cond;
+ pthread_t thr;
+ int event_pending;
+ int stop_thread;
+ int thread_running;
+ nfc_event_t event;
+ nfc_status_t event_status;
+} aidl_callback_data;
+
+static void* aidl_callback_thread_fct(void* arg) {
+ int ret;
+ struct aidl_callback_struct* pcb_data = (struct aidl_callback_struct*)arg;
+
+ ret = pthread_mutex_lock(&pcb_data->mutex);
+ if (ret != 0) {
+ LOG(ERROR) << StringPrintf("%s pthread_mutex_lock failed", __func__);
+ goto error;
+ }
+
+ do {
+ if (pcb_data->event_pending == 0) {
+ ret = pthread_cond_wait(&pcb_data->cond, &pcb_data->mutex);
+ if (ret != 0) {
+ LOG(ERROR) << StringPrintf("%s pthread_cond_wait failed", __func__);
+ break;
+ }
+ }
+
+ if (pcb_data->event_pending) {
+ nfc_event_t event = pcb_data->event;
+ nfc_status_t event_status = pcb_data->event_status;
+ int ending = pcb_data->stop_thread;
+ pcb_data->event_pending = 0;
+ ret = pthread_cond_signal(&pcb_data->cond);
+ if (ret != 0) {
+ LOG(ERROR) << StringPrintf("%s pthread_cond_signal failed", __func__);
+ break;
+ }
+ if (ending) {
+ pcb_data->thread_running = 0;
+ }
+ ret = pthread_mutex_unlock(&pcb_data->mutex);
+ if (ret != 0) {
+ LOG(ERROR) << StringPrintf("%s pthread_mutex_unlock failed", __func__);
+ }
+ LOG(INFO) << StringPrintf("%s event %hhx status %hhx", __func__, event,
+ event_status);
+ e_cback(event, event_status);
+ usleep(50000);
+ if (ending) {
+ return NULL;
+ }
+ ret = pthread_mutex_lock(&pcb_data->mutex);
+ if (ret != 0) {
+ LOG(ERROR) << StringPrintf("%s pthread_mutex_lock failed", __func__);
+ goto error;
+ }
+ }
+ } while (pcb_data->stop_thread == 0 || pcb_data->event_pending);
+
+ ret = pthread_mutex_unlock(&pcb_data->mutex);
+ if (ret != 0) {
+ LOG(ERROR) << StringPrintf("%s pthread_mutex_unlock failed", __func__);
+ }
+
+error:
+ pcb_data->thread_running = 0;
+ return NULL;
+}
+
+static int aidl_callback_thread_start() {
+ int ret;
+ LOG(INFO) << StringPrintf("%s", __func__);
+
+ memset(&aidl_callback_data, 0, sizeof(aidl_callback_data));
+
+ ret = pthread_mutex_init(&aidl_callback_data.mutex, NULL);
+ if (ret != 0) {
+ LOG(ERROR) << StringPrintf("%s pthread_mutex_init failed", __func__);
+ return ret;
+ }
+
+ ret = pthread_cond_init(&aidl_callback_data.cond, NULL);
+ if (ret != 0) {
+ LOG(ERROR) << StringPrintf("%s pthread_cond_init failed", __func__);
+ return ret;
+ }
+
+ aidl_callback_data.thread_running = 1;
+
+ ret = pthread_create(&aidl_callback_data.thr, NULL, aidl_callback_thread_fct,
+ &aidl_callback_data);
+ if (ret != 0) {
+ LOG(ERROR) << StringPrintf("%s pthread_create failed", __func__);
+ aidl_callback_data.thread_running = 0;
+ return ret;
+ }
+
+ return 0;
+}
+
+static int aidl_callback_thread_end() {
+ LOG(INFO) << StringPrintf("%s", __func__);
+ if (aidl_callback_data.thread_running != 0) {
+ int ret;
+
+ ret = pthread_mutex_lock(&aidl_callback_data.mutex);
+ if (ret != 0) {
+ LOG(ERROR) << StringPrintf("%s pthread_mutex_lock failed", __func__);
+ return ret;
+ }
+
+ aidl_callback_data.stop_thread = 1;
+
+ // Wait for the thread to have no event pending
+ while (aidl_callback_data.thread_running &&
+ aidl_callback_data.event_pending) {
+ ret = pthread_cond_signal(&aidl_callback_data.cond);
+ if (ret != 0) {
+ LOG(ERROR) << StringPrintf("%s pthread_cond_signal failed", __func__);
+ return ret;
+ }
+ ret = pthread_cond_wait(&aidl_callback_data.cond,
+ &aidl_callback_data.mutex);
+ if (ret != 0) {
+ LOG(ERROR) << StringPrintf("%s pthread_cond_wait failed", __func__);
+ break;
+ }
+ }
+
+ ret = pthread_mutex_unlock(&aidl_callback_data.mutex);
+ if (ret != 0) {
+ LOG(ERROR) << StringPrintf("%s pthread_mutex_unlock failed", __func__);
+ return ret;
+ }
+
+ ret = pthread_cond_signal(&aidl_callback_data.cond);
+ if (ret != 0) {
+ LOG(ERROR) << StringPrintf("%s pthread_cond_signal failed", __func__);
+ return ret;
+ }
+
+ ret = pthread_detach(aidl_callback_data.thr);
+ if (ret != 0) {
+ LOG(ERROR) << StringPrintf("%s pthread_detach failed", __func__);
+ return ret;
+ }
+ }
+ return 0;
+}
+
+static void aidl_callback_post(nfc_event_t event, nfc_status_t event_status) {
+ int ret;
+
+ if (pthread_equal(pthread_self(), aidl_callback_data.thr)) {
+ e_cback(event, event_status);
+ }
+
+ ret = pthread_mutex_lock(&aidl_callback_data.mutex);
+ if (ret != 0) {
+ LOG(ERROR) << StringPrintf("%s pthread_mutex_lock failed", __func__);
+ return;
+ }
+
+ if (aidl_callback_data.thread_running == 0) {
+ (void)pthread_mutex_unlock(&aidl_callback_data.mutex);
+ LOG(ERROR) << StringPrintf("%s thread is not running", __func__);
+ e_cback(event, event_status);
+ return;
+ }
+
+ while (aidl_callback_data.event_pending) {
+ ret =
+ pthread_cond_wait(&aidl_callback_data.cond, &aidl_callback_data.mutex);
+ if (ret != 0) {
+ LOG(ERROR) << StringPrintf("%s pthread_cond_wait failed", __func__);
+ return;
+ }
+ }
+
+ aidl_callback_data.event_pending = 1;
+ aidl_callback_data.event = event;
+ aidl_callback_data.event_status = event_status;
+
+ ret = pthread_mutex_unlock(&aidl_callback_data.mutex);
+ if (ret != 0) {
+ LOG(ERROR) << StringPrintf("%s pthread_mutex_unlock failed", __func__);
+ return;
+ }
+
+ ret = pthread_cond_signal(&aidl_callback_data.cond);
+ if (ret != 0) {
+ LOG(ERROR) << StringPrintf("%s pthread_cond_signal failed", __func__);
+ return;
+ }
+}
+
+int Cf_hal_open(nfc_stack_callback_t* p_cback,
+ nfc_stack_data_callback_t* p_data_cback) {
+ LOG(INFO) << StringPrintf("%s", __func__);
+ pthread_mutex_lock(&hmutex);
+ if (hal_opened) {
+ // already opened, close then open again
+ LOG(INFO) << StringPrintf("%s close and open again", __func__);
+ if (aidl_callback_data.thread_running && aidl_callback_thread_end() != 0) {
+ pthread_mutex_unlock(&hmutex);
+ return -1;
+ }
+ hal_opened = false;
+ }
+ e_cback = p_cback;
+ d_cback = p_data_cback;
+ if ((hal_opened || !aidl_callback_data.thread_running) &&
+ (aidl_callback_thread_start() != 0)) {
+ // status failed
+ LOG(INFO) << StringPrintf("%s failed", __func__);
+ aidl_callback_post(HAL_NFC_OPEN_CPLT_EVT, HAL_NFC_STATUS_FAILED);
+ pthread_mutex_unlock(&hmutex);
+ return -1;
+ }
+ hal_opened = true;
+ aidl_callback_post(HAL_NFC_OPEN_CPLT_EVT, HAL_NFC_STATUS_OK);
+ pthread_mutex_unlock(&hmutex);
+ return 0;
+}
+
+int Cf_hal_write(uint16_t data_len, const uint8_t* p_data) {
+ if (!hal_opened) return -1;
+ // TODO: write NCI state machine
+ (void)data_len;
+ (void)p_data;
+ return 0;
+}
+
+int Cf_hal_core_initialized() {
+ if (!hal_opened) return -1;
+ pthread_mutex_lock(&hmutex);
+ aidl_callback_post(HAL_NFC_POST_INIT_CPLT_EVT, HAL_NFC_STATUS_OK);
+ pthread_mutex_unlock(&hmutex);
+ return 0;
+}
+
+int Cf_hal_pre_discover() {
+ if (!hal_opened) return -1;
+ pthread_mutex_lock(&hmutex);
+ aidl_callback_post(HAL_NFC_PRE_DISCOVER_CPLT_EVT, HAL_NFC_STATUS_OK);
+ pthread_mutex_unlock(&hmutex);
+ return 0;
+}
+
+int Cf_hal_close() {
+ LOG(INFO) << StringPrintf("%s", __func__);
+ if (!hal_opened) return -1;
+ pthread_mutex_lock(&hmutex);
+ hal_opened = false;
+ aidl_callback_post(HAL_NFC_CLOSE_CPLT_EVT, HAL_NFC_STATUS_OK);
+ if (aidl_callback_data.thread_running && aidl_callback_thread_end() != 0) {
+ LOG(ERROR) << StringPrintf("%s thread end failed", __func__);
+ pthread_mutex_unlock(&hmutex);
+ return -1;
+ }
+ pthread_mutex_unlock(&hmutex);
+ return 0;
+}
+
+int Cf_hal_close_off() {
+ LOG(INFO) << StringPrintf("%s", __func__);
+ if (!hal_opened) return -1;
+ pthread_mutex_lock(&hmutex);
+ hal_opened = false;
+ aidl_callback_post(HAL_NFC_CLOSE_CPLT_EVT, HAL_NFC_STATUS_OK);
+ if (aidl_callback_data.thread_running && aidl_callback_thread_end() != 0) {
+ LOG(ERROR) << StringPrintf("%s thread end failed", __func__);
+ pthread_mutex_unlock(&hmutex);
+ return -1;
+ }
+ pthread_mutex_unlock(&hmutex);
+ return 0;
+}
+
+int Cf_hal_power_cycle() {
+ if (!hal_opened) return -1;
+ pthread_mutex_lock(&hmutex);
+ aidl_callback_post(HAL_NFC_OPEN_CPLT_EVT, HAL_NFC_STATUS_OK);
+ pthread_mutex_unlock(&hmutex);
+ return 0;
+}
+
+void Cf_hal_factoryReset() {}
+void Cf_hal_getConfig(NfcConfig& config) {
+ // TODO: read config from /vendor/etc/libnfc-hal-cf.conf
+ memset(&config, 0x00, sizeof(NfcConfig));
+ config.nfaPollBailOutMode = 1;
+ config.maxIsoDepTransceiveLength = 0xFEFF;
+ config.defaultOffHostRoute = 0x81;
+ config.defaultOffHostRouteFelica = 0x81;
+ config.defaultSystemCodeRoute = 0x00;
+ config.defaultSystemCodePowerState = 0x3B;
+ config.defaultRoute = 0x00;
+ config.offHostRouteUicc.resize(1);
+ config.offHostRouteUicc[0] = 0x81;
+ config.offHostRouteEse.resize(1);
+ config.offHostRouteEse[0] = 0x81;
+ config.defaultIsoDepRoute = 0x81;
+}
+
+void Cf_hal_setVerboseLogging(bool enable) { dbg_logging = enable; }
+
+bool Cf_hal_getVerboseLogging() { return dbg_logging; }
diff --git a/guest/hals/nfc/Cf_hal_api.h b/guest/hals/nfc/Cf_hal_api.h
new file mode 100644
index 0000000..519e853
--- /dev/null
+++ b/guest/hals/nfc/Cf_hal_api.h
@@ -0,0 +1,56 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef _VENDOR_HAL_API_H_
+#define _VENDOR_HAL_API_H_
+
+#include <aidl/android/hardware/nfc/INfc.h>
+#include <aidl/android/hardware/nfc/NfcConfig.h>
+#include <aidl/android/hardware/nfc/NfcEvent.h>
+#include <aidl/android/hardware/nfc/NfcStatus.h>
+#include <aidl/android/hardware/nfc/PresenceCheckAlgorithm.h>
+#include <aidl/android/hardware/nfc/ProtocolDiscoveryConfig.h>
+#include "hardware_nfc.h"
+
+using aidl::android::hardware::nfc::NfcConfig;
+using aidl::android::hardware::nfc::NfcEvent;
+using aidl::android::hardware::nfc::NfcStatus;
+using aidl::android::hardware::nfc::PresenceCheckAlgorithm;
+using aidl::android::hardware::nfc::ProtocolDiscoveryConfig;
+
+int Cf_hal_open(nfc_stack_callback_t* p_cback,
+ nfc_stack_data_callback_t* p_data_cback);
+int Cf_hal_write(uint16_t data_len, const uint8_t* p_data);
+
+int Cf_hal_core_initialized();
+
+int Cf_hal_pre_discover();
+
+int Cf_hal_close();
+
+int Cf_hal_close_off();
+
+int Cf_hal_power_cycle();
+
+void Cf_hal_factoryReset();
+
+void Cf_hal_getConfig(NfcConfig& config);
+
+void Cf_hal_setVerboseLogging(bool enable);
+
+bool Cf_hal_getVerboseLogging();
+
+#endif /* _VENDOR_HAL_API_H_ */
diff --git a/guest/hals/nfc/Nfc.cc b/guest/hals/nfc/Nfc.cc
new file mode 100644
index 0000000..993d80b
--- /dev/null
+++ b/guest/hals/nfc/Nfc.cc
@@ -0,0 +1,171 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "Nfc.h"
+
+#include <android-base/logging.h>
+
+#include "Cf_hal_api.h"
+
+namespace aidl {
+namespace android {
+namespace hardware {
+namespace nfc {
+
+std::shared_ptr<INfcClientCallback> Nfc::mCallback = nullptr;
+AIBinder_DeathRecipient* clientDeathRecipient = nullptr;
+
+void OnDeath(void* cookie) {
+ if (Nfc::mCallback != nullptr &&
+ !AIBinder_isAlive(Nfc::mCallback->asBinder().get())) {
+ LOG(INFO) << __func__ << " Nfc service has died";
+ Nfc* nfc = static_cast<Nfc*>(cookie);
+ nfc->close(NfcCloseType::DISABLE);
+ }
+}
+
+::ndk::ScopedAStatus Nfc::open(
+ const std::shared_ptr<INfcClientCallback>& clientCallback) {
+ LOG(INFO) << "open";
+ if (clientCallback == nullptr) {
+ LOG(INFO) << "Nfc::open null callback";
+ return ndk::ScopedAStatus::fromServiceSpecificError(
+ static_cast<int32_t>(NfcStatus::FAILED));
+ } else {
+ Nfc::mCallback = clientCallback;
+
+ if (clientDeathRecipient != nullptr) {
+ AIBinder_DeathRecipient_delete(clientDeathRecipient);
+ clientDeathRecipient = nullptr;
+ }
+ clientDeathRecipient = AIBinder_DeathRecipient_new(OnDeath);
+ auto linkRet =
+ AIBinder_linkToDeath(clientCallback->asBinder().get(),
+ clientDeathRecipient, this /* cookie */);
+ if (linkRet != STATUS_OK) {
+ LOG(ERROR) << __func__ << ": linkToDeath failed: " << linkRet;
+ // Just ignore the error.
+ }
+
+ int ret = Cf_hal_open(eventCallback, dataCallback);
+ return ret == 0 ? ndk::ScopedAStatus::ok()
+ : ndk::ScopedAStatus::fromServiceSpecificError(
+ static_cast<int32_t>(NfcStatus::FAILED));
+ return ndk::ScopedAStatus::ok();
+ }
+ return ndk::ScopedAStatus::ok();
+}
+
+::ndk::ScopedAStatus Nfc::close(NfcCloseType type) {
+ LOG(INFO) << "close";
+ if (Nfc::mCallback == nullptr) {
+ LOG(ERROR) << __func__ << " mCallback null";
+ return ndk::ScopedAStatus::fromServiceSpecificError(
+ static_cast<int32_t>(NfcStatus::FAILED));
+ }
+ int ret = 0;
+ if (type == NfcCloseType::HOST_SWITCHED_OFF) {
+ ret = Cf_hal_close_off();
+ } else {
+ ret = Cf_hal_close();
+ }
+ AIBinder_DeathRecipient_delete(clientDeathRecipient);
+ clientDeathRecipient = nullptr;
+ return ret == 0 ? ndk::ScopedAStatus::ok()
+ : ndk::ScopedAStatus::fromServiceSpecificError(
+ static_cast<int32_t>(NfcStatus::FAILED));
+}
+
+::ndk::ScopedAStatus Nfc::coreInitialized() {
+ LOG(INFO) << "coreInitialized";
+ if (Nfc::mCallback == nullptr) {
+ LOG(ERROR) << __func__ << "mCallback null";
+ return ndk::ScopedAStatus::fromServiceSpecificError(
+ static_cast<int32_t>(NfcStatus::FAILED));
+ }
+ int ret = Cf_hal_core_initialized();
+
+ return ret == 0 ? ndk::ScopedAStatus::ok()
+ : ndk::ScopedAStatus::fromServiceSpecificError(
+ static_cast<int32_t>(NfcStatus::FAILED));
+}
+
+::ndk::ScopedAStatus Nfc::factoryReset() {
+ LOG(INFO) << "factoryReset";
+ Cf_hal_factoryReset();
+ return ndk::ScopedAStatus::ok();
+}
+
+::ndk::ScopedAStatus Nfc::getConfig(NfcConfig* _aidl_return) {
+ LOG(INFO) << "getConfig";
+ NfcConfig nfcVendorConfig;
+ Cf_hal_getConfig(nfcVendorConfig);
+
+ *_aidl_return = nfcVendorConfig;
+ return ndk::ScopedAStatus::ok();
+}
+
+::ndk::ScopedAStatus Nfc::powerCycle() {
+ LOG(INFO) << "powerCycle";
+ if (Nfc::mCallback == nullptr) {
+ LOG(ERROR) << __func__ << "mCallback null";
+ return ndk::ScopedAStatus::fromServiceSpecificError(
+ static_cast<int32_t>(NfcStatus::FAILED));
+ }
+ return Cf_hal_power_cycle() ? ndk::ScopedAStatus::fromServiceSpecificError(
+ static_cast<int32_t>(NfcStatus::FAILED))
+ : ndk::ScopedAStatus::ok();
+}
+
+::ndk::ScopedAStatus Nfc::preDiscover() {
+ LOG(INFO) << "preDiscover";
+ if (Nfc::mCallback == nullptr) {
+ LOG(ERROR) << __func__ << "mCallback null";
+ return ndk::ScopedAStatus::fromServiceSpecificError(
+ static_cast<int32_t>(NfcStatus::FAILED));
+ }
+ return Cf_hal_pre_discover() ? ndk::ScopedAStatus::fromServiceSpecificError(
+ static_cast<int32_t>(NfcStatus::FAILED))
+ : ndk::ScopedAStatus::ok();
+}
+
+::ndk::ScopedAStatus Nfc::write(const std::vector<uint8_t>& data,
+ int32_t* _aidl_return) {
+ LOG(INFO) << "write";
+ if (Nfc::mCallback == nullptr) {
+ LOG(ERROR) << __func__ << "mCallback null";
+ return ndk::ScopedAStatus::fromServiceSpecificError(
+ static_cast<int32_t>(NfcStatus::FAILED));
+ }
+ *_aidl_return = Cf_hal_write(data.size(), &data[0]);
+ return ndk::ScopedAStatus::ok();
+}
+
+::ndk::ScopedAStatus Nfc::setEnableVerboseLogging(bool enable) {
+ LOG(INFO) << "setVerboseLogging";
+ Cf_hal_setVerboseLogging(enable);
+ return ndk::ScopedAStatus::ok();
+}
+
+::ndk::ScopedAStatus Nfc::isVerboseLoggingEnabled(bool* _aidl_return) {
+ *_aidl_return = Cf_hal_getVerboseLogging();
+ return ndk::ScopedAStatus::ok();
+}
+
+} // namespace nfc
+} // namespace hardware
+} // namespace android
+} // namespace aidl
diff --git a/guest/hals/nfc/Nfc.h b/guest/hals/nfc/Nfc.h
new file mode 100644
index 0000000..cf473be
--- /dev/null
+++ b/guest/hals/nfc/Nfc.h
@@ -0,0 +1,72 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <aidl/android/hardware/nfc/BnNfc.h>
+#include <aidl/android/hardware/nfc/INfcClientCallback.h>
+#include <android-base/logging.h>
+namespace aidl {
+namespace android {
+namespace hardware {
+namespace nfc {
+
+using ::aidl::android::hardware::nfc::NfcCloseType;
+using ::aidl::android::hardware::nfc::NfcConfig;
+using ::aidl::android::hardware::nfc::NfcStatus;
+
+// Default implementation that reports no support NFC.
+struct Nfc : public BnNfc {
+ public:
+ Nfc() = default;
+
+ ::ndk::ScopedAStatus open(
+ const std::shared_ptr<INfcClientCallback>& clientCallback) override;
+ ::ndk::ScopedAStatus close(NfcCloseType type) override;
+ ::ndk::ScopedAStatus coreInitialized() override;
+ ::ndk::ScopedAStatus factoryReset() override;
+ ::ndk::ScopedAStatus getConfig(NfcConfig* _aidl_return) override;
+ ::ndk::ScopedAStatus powerCycle() override;
+ ::ndk::ScopedAStatus preDiscover() override;
+ ::ndk::ScopedAStatus write(const std::vector<uint8_t>& data,
+ int32_t* _aidl_return) override;
+ ::ndk::ScopedAStatus setEnableVerboseLogging(bool enable) override;
+ ::ndk::ScopedAStatus isVerboseLoggingEnabled(bool* _aidl_return) override;
+ static void eventCallback(uint8_t event, uint8_t status) {
+ if (mCallback != nullptr) {
+ auto ret = mCallback->sendEvent((NfcEvent)event, (NfcStatus)status);
+ if (!ret.isOk()) {
+ LOG(ERROR) << "Failed to send event!";
+ }
+ }
+ }
+
+ static void dataCallback(uint16_t data_len, uint8_t* p_data) {
+ std::vector<uint8_t> data(p_data, p_data + data_len);
+ if (mCallback != nullptr) {
+ auto ret = mCallback->sendData(data);
+ if (!ret.isOk()) {
+ LOG(ERROR) << "Failed to send data!";
+ }
+ }
+ }
+ static std::shared_ptr<INfcClientCallback> mCallback;
+};
+
+} // namespace nfc
+} // namespace hardware
+} // namespace android
+} // namespace aidl
diff --git a/guest/hals/nfc/OWNERS b/guest/hals/nfc/OWNERS
new file mode 100644
index 0000000..2c46507
--- /dev/null
+++ b/guest/hals/nfc/OWNERS
@@ -0,0 +1 @@
+include platform/packages/apps/Nfc:/OWNERS
diff --git a/guest/hals/nfc/conf/Android.bp b/guest/hals/nfc/conf/Android.bp
new file mode 100644
index 0000000..cc0a2b4
--- /dev/null
+++ b/guest/hals/nfc/conf/Android.bp
@@ -0,0 +1,12 @@
+package {
+ default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+prebuilt_etc {
+ name: "libnfc-hal-cf.conf-default",
+ src: "libnfc-hal-cf.conf",
+ proprietary: true,
+ vendor: true,
+ filename: "libnfc-hal-cf.conf",
+
+}
diff --git a/guest/hals/nfc/conf/libnfc-hal-cf.conf b/guest/hals/nfc/conf/libnfc-hal-cf.conf
new file mode 100644
index 0000000..e79651e
--- /dev/null
+++ b/guest/hals/nfc/conf/libnfc-hal-cf.conf
@@ -0,0 +1,120 @@
+########################### Start of libnf-hal-cf.conf ###########################
+
+###############################################################################
+###############################################################################
+# CF HAL trace log level
+CFNFC_HAL_LOGLEVEL=4
+NFC_DEBUG_ENABLED=0
+
+###############################################################################
+# File used for NFA storage
+NFA_STORAGE="/data/nfc"
+
+###############################################################################
+# Keep the nfa storage file.
+PRESERVE_STORAGE=1
+
+###############################################################################
+# Vendor Specific Proprietary Protocol & Discovery Configuration
+# Set to 0xFF if unsupported
+# byte[0] NCI_PROTOCOL_18092_ACTIVE
+# byte[1] NCI_PROTOCOL_B_PRIME
+# byte[2] NCI_PROTOCOL_DUAL
+# byte[3] NCI_PROTOCOL_15693
+# byte[4] NCI_PROTOCOL_KOVIO
+# byte[5] NCI_PROTOCOL_MIFARE
+# byte[6] NCI_DISCOVERY_TYPE_POLL_KOVIO
+# byte[7] NCI_DISCOVERY_TYPE_POLL_B_PRIME
+# byte[8] NCI_DISCOVERY_TYPE_LISTEN_B_PRIME
+NFA_PROPRIETARY_CFG={05:FF:FF:06:8A:90:77:FF:FF}
+
+###############################################################################
+# Choose the presence-check algorithm for type-4 tag. If not defined,
+# the default value is 1.
+# 0 NFA_RW_PRES_CHK_DEFAULT; Let stack selects an algorithm
+# 1 NFA_RW_PRES_CHK_I_BLOCK; ISO-DEP protocol's empty I-block
+# 2 NFA_RW_PRES_CHK_RESET; Deactivate to Sleep, then re-activate
+# 3 NFA_RW_PRES_CHK_RB_CH0; Type-4 tag protocol's ReadBinary command on channel 0
+# 4 NFA_RW_PRES_CHK_RB_CH3; Type-4 tag protocol's ReadBinary command on channel 3
+# 5 NFA_RW_PRES_CHK_ISO_DEP_NAK; presence check command ISO-DEP NAK as per NCI2.0
+PRESENCE_CHECK_ALGORITHM=5
+
+###############################################################################
+# Name of the NCI HAL module to use
+# If unset, falls back to nfc_nci.bcm2079x
+NCI_HAL_MODULE="nfc_nci.st21nfc"
+
+###############################################################################
+# White list to be set at startup.
+DEVICE_HOST_ALLOW_LIST={02:C0}
+
+###############################################################################
+# BAIL OUT value for P2P
+# Implements algorithm for NFC-DEP protocol priority over ISO-DEP protocol.
+POLL_BAIL_OUT_MODE=1
+
+###############################################################################
+# Extended APDU length for ISO_DEP
+ISO_DEP_MAX_TRANSCEIVE=0xFEFF
+
+###############################################################################
+# Configure the NFC Extras to open and use a static pipe. If the value is
+# not set or set to 0, then the default is use a dynamic pipe based on a
+# destination gate (see NFA_HCI_DEFAULT_DEST_GATE). Note there is a value
+# for each EE (ESE/SIM)
+OFF_HOST_ESE_PIPE_ID=0x5E
+OFF_HOST_SIM_PIPE_ID=0x3E
+
+###############################################################################
+#Set the default Felica T3T System Code OffHost route Location :
+#This settings will be used when application does not set this parameter
+# host 0x00
+# eSE 0x82 (eSE), 0x86 (eUICC/SPI-SE)
+# UICC 0x81 (UICC_1), 0x85 (UICC_2)
+DEFAULT_SYS_CODE_ROUTE=0x86
+
+###############################################################################
+#Set the Felica T3T System Code supported power state:
+DEFAULT_SYS_CODE_PWR_STATE=0x3B
+
+###############################################################################
+# Default off-host route for Felica.
+# This settings will be used when application does not set this parameter
+# host 0x00
+# eSE 0x82 (eSE), 0x86 (eUICC/SPI-SE)
+# UICC 0x81 (UICC_1), 0x85 (UICC_2)
+DEFAULT_NFCF_ROUTE=0x86
+
+###############################################################################
+# Configure the default off-host route.
+# used for technology A and B routing
+# eSE 0x82 (eSE), 0x86 (eUICC/SPI-SE)
+# UICC 0x81 (UICC_1), 0x85 (UICC_2)
+DEFAULT_OFFHOST_ROUTE=0x81
+
+###############################################################################
+# Configure the default AID route.
+# host 0x00
+# eSE 0x82 (eSE), 0x86 (eUICC/SPI-SE)
+# UICC 0x81 (UICC_1), 0x85 (UICC_2)
+DEFAULT_ROUTE=0x00
+
+###############################################################################
+# Configure the NFCEEIDs of offhost UICC.
+# UICC 0x81 (UICC_1), 0x85 (UICC_2)
+OFFHOST_ROUTE_UICC={81}
+
+###############################################################################
+# Configure the NFCEEIDs of offhost eSEs.
+# eSE 0x82 (eSE), 0x86 (eUICC/SPI-SE)
+OFFHOST_ROUTE_ESE={86}
+
+###############################################################################
+# Configure the list of NFCEE for the ISO-DEP routing.
+# host 0x00
+# eSE 0x82 (eSE), 0x86 (eUICC/SPI-SE)
+# UICC 0x81 (UICC_1), 0x85 (UICC_2)
+DEFAULT_ISODEP_ROUTE=0x81
+
+
+
diff --git a/guest/hals/nfc/hardware_nfc.h b/guest/hals/nfc/hardware_nfc.h
new file mode 100644
index 0000000..b266541
--- /dev/null
+++ b/guest/hals/nfc/hardware_nfc.h
@@ -0,0 +1,51 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#pragma once
+
+typedef uint8_t nfc_event_t;
+typedef uint8_t nfc_status_t;
+
+/*
+ * The callback passed in from the NFC stack that the HAL
+ * can use to pass events back to the stack.
+ */
+typedef void(nfc_stack_callback_t)(nfc_event_t event,
+ nfc_status_t event_status);
+
+/*
+ * The callback passed in from the NFC stack that the HAL
+ * can use to pass incomming data to the stack.
+ */
+typedef void(nfc_stack_data_callback_t)(uint16_t data_len, uint8_t* p_data);
+
+enum {
+ HAL_NFC_OPEN_CPLT_EVT = 0u,
+ HAL_NFC_CLOSE_CPLT_EVT = 1u,
+ HAL_NFC_POST_INIT_CPLT_EVT = 2u,
+ HAL_NFC_PRE_DISCOVER_CPLT_EVT = 3u,
+ HAL_NFC_REQUEST_CONTROL_EVT = 4u,
+ HAL_NFC_RELEASE_CONTROL_EVT = 5u,
+ HAL_NFC_ERROR_EVT = 6u,
+ HAL_HCI_NETWORK_RESET = 7u,
+};
+
+enum {
+ HAL_NFC_STATUS_OK = 0u,
+ HAL_NFC_STATUS_FAILED = 1u,
+ HAL_NFC_STATUS_ERR_TRANSPORT = 2u,
+ HAL_NFC_STATUS_ERR_CMD_TIMEOUT = 3u,
+ HAL_NFC_STATUS_REFUSED = 4u,
+};
diff --git a/guest/hals/nfc/main.cc b/guest/hals/nfc/main.cc
new file mode 100644
index 0000000..51ec8ce
--- /dev/null
+++ b/guest/hals/nfc/main.cc
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <android-base/logging.h>
+#include <android/binder_manager.h>
+#include <android/binder_process.h>
+
+#include "Nfc.h"
+using ::aidl::android::hardware::nfc::Nfc;
+
+int main() {
+ LOG(INFO) << "NFC HAL starting up";
+ if (!ABinderProcess_setThreadPoolMaxThreadCount(1)) {
+ LOG(INFO) << "failed to set thread pool max thread count";
+ return 1;
+ }
+ std::shared_ptr<Nfc> nfc_service = ndk::SharedRefBase::make<Nfc>();
+
+ const std::string instance = std::string() + Nfc::descriptor + "/default";
+ CHECK_EQ(STATUS_OK, AServiceManager_addService(nfc_service->asBinder().get(),
+ instance.c_str()));
+ ABinderProcess_joinThreadPool();
+ return 0;
+}
diff --git a/guest/hals/nfc/nfc-service-cuttlefish.rc b/guest/hals/nfc/nfc-service-cuttlefish.rc
new file mode 100644
index 0000000..e8f6505
--- /dev/null
+++ b/guest/hals/nfc/nfc-service-cuttlefish.rc
@@ -0,0 +1,4 @@
+service nfc_hal_service /vendor/bin/hw/android.hardware.nfc-service.cuttlefish
+ class hal
+ user nfc
+ group nfc
diff --git a/guest/hals/nfc/nfc-service-cuttlefish.xml b/guest/hals/nfc/nfc-service-cuttlefish.xml
new file mode 100644
index 0000000..70fed20
--- /dev/null
+++ b/guest/hals/nfc/nfc-service-cuttlefish.xml
@@ -0,0 +1,6 @@
+<manifest version="1.0" type="device">
+ <hal format="aidl">
+ <name>android.hardware.nfc</name>
+ <fqname>INfc/default</fqname>
+ </hal>
+</manifest>
diff --git a/guest/hals/ril/reference-libril/Android.bp b/guest/hals/ril/reference-libril/Android.bp
index 687f95f..ab5ac03 100644
--- a/guest/hals/ril/reference-libril/Android.bp
+++ b/guest/hals/ril/reference-libril/Android.bp
@@ -24,6 +24,7 @@
"-Wno-unused-parameter",
],
srcs: [
+ "RefRadioNetwork.cpp",
"ril.cpp",
"RilSapSocket.cpp",
"ril_config.cpp",
@@ -36,6 +37,14 @@
"hardware/ril/include",
],
shared_libs: [
+ "android.hardware.radio-library.compat",
+ "android.hardware.radio.config-V1-ndk",
+ "android.hardware.radio.data-V1-ndk",
+ "android.hardware.radio.messaging-V1-ndk",
+ "android.hardware.radio.modem-V1-ndk",
+ "android.hardware.radio.network-V1-ndk",
+ "android.hardware.radio.sim-V1-ndk",
+ "android.hardware.radio.voice-V1-ndk",
"[email protected]",
"[email protected]",
"[email protected]",
@@ -48,6 +57,8 @@
"[email protected]",
"[email protected]",
"[email protected]",
+ "libbase",
+ "libbinder_ndk",
"libcutils",
"libhardware_legacy",
"libhidlbase",
@@ -56,15 +67,13 @@
"libutils",
],
static_libs: [
- "libprotobuf-c-nano-enable_malloc"
+ "libprotobuf-c-nano-enable_malloc",
],
}
filegroup {
name: "libril-modem-lib-manifests",
srcs: [
- "[email protected]",
- "[email protected]",
+ "[email protected]",
],
}
-
diff --git a/guest/hals/ril/reference-libril/RefRadioNetwork.cpp b/guest/hals/ril/reference-libril/RefRadioNetwork.cpp
new file mode 100644
index 0000000..4f39944
--- /dev/null
+++ b/guest/hals/ril/reference-libril/RefRadioNetwork.cpp
@@ -0,0 +1,50 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "RefRadioNetwork.h"
+
+namespace cf::ril {
+
+using ::ndk::ScopedAStatus;
+using namespace ::aidl::android::hardware::radio;
+constexpr auto ok = &ScopedAStatus::ok;
+
+static RadioResponseInfo responseInfo(int32_t serial, RadioError error = RadioError::NONE) {
+ return {
+ .type = RadioResponseType::SOLICITED,
+ .serial = serial,
+ .error = error,
+ };
+}
+
+ScopedAStatus RefRadioNetwork::setUsageSetting(int32_t serial, network::UsageSetting usageSetting) {
+ if (usageSetting != network::UsageSetting::VOICE_CENTRIC &&
+ usageSetting != network::UsageSetting::DATA_CENTRIC) {
+ respond()->setUsageSettingResponse(responseInfo(serial, RadioError::INVALID_ARGUMENTS));
+ return ok();
+ }
+
+ mUsageSetting = usageSetting;
+ respond()->setUsageSettingResponse(responseInfo(serial));
+ return ok();
+}
+
+ScopedAStatus RefRadioNetwork::getUsageSetting(int32_t serial) {
+ respond()->getUsageSettingResponse(responseInfo(serial), mUsageSetting);
+ return ok();
+}
+
+} // namespace cf::ril
diff --git a/guest/hals/ril/reference-libril/RefRadioNetwork.h b/guest/hals/ril/reference-libril/RefRadioNetwork.h
new file mode 100644
index 0000000..5f16b14
--- /dev/null
+++ b/guest/hals/ril/reference-libril/RefRadioNetwork.h
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#pragma once
+
+#include <libradiocompat/RadioNetwork.h>
+
+namespace cf::ril {
+
+class RefRadioNetwork : public android::hardware::radio::compat::RadioNetwork {
+ ::aidl::android::hardware::radio::network::UsageSetting mUsageSetting =
+ ::aidl::android::hardware::radio::network::UsageSetting::VOICE_CENTRIC;
+
+ public:
+ using android::hardware::radio::compat::RadioNetwork::RadioNetwork;
+
+ ::ndk::ScopedAStatus setUsageSetting(
+ int32_t serial,
+ ::aidl::android::hardware::radio::network::UsageSetting usageSetting) override;
+ ::ndk::ScopedAStatus getUsageSetting(int32_t serial) override;
+};
+
+} // namespace cf::ril
diff --git a/guest/hals/ril/reference-libril/[email protected] b/guest/hals/ril/reference-libril/[email protected]
deleted file mode 100644
index 8652229..0000000
--- a/guest/hals/ril/reference-libril/[email protected]
+++ /dev/null
@@ -1,18 +0,0 @@
-<manifest version="1.0" type="device">
- <hal format="hidl">
- <name>android.hardware.radio</name>
- <transport>hwbinder</transport>
- <version>1.6</version>
- <interface>
- <name>IRadio</name>
- <instance>slot1</instance>
- <!-- cuttlefish doesn't support SIM slot 2/3 -->
- </interface>
- <!-- TODO (b/130079344):
- <interface>
- <name>ISap</name>
- <instance>slot1</instance>
- </interface>
- -->
- </hal>
-</manifest>
diff --git a/guest/hals/ril/reference-libril/[email protected] b/guest/hals/ril/reference-libril/[email protected]
new file mode 100644
index 0000000..8501dec
--- /dev/null
+++ b/guest/hals/ril/reference-libril/[email protected]
@@ -0,0 +1,30 @@
+<manifest version="1.0" type="device">
+ <hal format="aidl">
+ <name>android.hardware.radio.config</name>
+ <fqname>IRadioConfig/default</fqname>
+ </hal>
+ <hal format="aidl">
+ <name>android.hardware.radio.data</name>
+ <fqname>IRadioData/slot1</fqname>
+ </hal>
+ <hal format="aidl">
+ <name>android.hardware.radio.messaging</name>
+ <fqname>IRadioMessaging/slot1</fqname>
+ </hal>
+ <hal format="aidl">
+ <name>android.hardware.radio.modem</name>
+ <fqname>IRadioModem/slot1</fqname>
+ </hal>
+ <hal format="aidl">
+ <name>android.hardware.radio.network</name>
+ <fqname>IRadioNetwork/slot1</fqname>
+ </hal>
+ <hal format="aidl">
+ <name>android.hardware.radio.sim</name>
+ <fqname>IRadioSim/slot1</fqname>
+ </hal>
+ <hal format="aidl">
+ <name>android.hardware.radio.voice</name>
+ <fqname>IRadioVoice/slot1</fqname>
+ </hal>
+</manifest>
diff --git a/guest/hals/ril/reference-libril/ril.h b/guest/hals/ril/reference-libril/ril.h
index 586de42..f7bc2c5 100644
--- a/guest/hals/ril/reference-libril/ril.h
+++ b/guest/hals/ril/reference-libril/ril.h
@@ -6202,7 +6202,7 @@
*
* "data" is NULL
*
- * "response" is an array of RIL_CellInfo_v12.
+ * "response" is an array of RIL_CellInfo_v12.
*
* Valid errors:
* SUCCESS
@@ -7543,8 +7543,12 @@
#define RIL_REQUEST_UPDATE_SIM_PHONEBOOK_RECORDS 172
+/**
+ * Same as RIL_REQUEST_GET_CELL_INFO_LIST but "response" is an array of RIL_CellInfo_v16.
+ */
+#define RIL_REQUEST_GET_CELL_INFO_LIST_1_6 173
-#define RIL_REQUEST_LAST RIL_REQUEST_UPDATE_SIM_PHONEBOOK_RECORDS
+#define RIL_REQUEST_LAST RIL_REQUEST_GET_CELL_INFO_LIST_1_6
/***********************************************************************/
diff --git a/guest/hals/ril/reference-libril/ril_commands.h b/guest/hals/ril/reference-libril/ril_commands.h
index a100b48..4b1c48d 100644
--- a/guest/hals/ril/reference-libril/ril_commands.h
+++ b/guest/hals/ril/reference-libril/ril_commands.h
@@ -14,176 +14,193 @@
** See the License for the specific language governing permissions and
** limitations under the License.
*/
- {0, NULL}, //none
- {RIL_REQUEST_GET_SIM_STATUS, radio_1_6::getIccCardStatusResponse},
- {RIL_REQUEST_ENTER_SIM_PIN, radio_1_6::supplyIccPinForAppResponse},
- {RIL_REQUEST_ENTER_SIM_PUK, radio_1_6::supplyIccPukForAppResponse},
- {RIL_REQUEST_ENTER_SIM_PIN2, radio_1_6::supplyIccPin2ForAppResponse},
- {RIL_REQUEST_ENTER_SIM_PUK2, radio_1_6::supplyIccPuk2ForAppResponse},
- {RIL_REQUEST_CHANGE_SIM_PIN, radio_1_6::changeIccPinForAppResponse},
- {RIL_REQUEST_CHANGE_SIM_PIN2, radio_1_6::changeIccPin2ForAppResponse},
- {RIL_REQUEST_ENTER_NETWORK_DEPERSONALIZATION, radio_1_6::supplyNetworkDepersonalizationResponse},
- {RIL_REQUEST_GET_CURRENT_CALLS, radio_1_6::getCurrentCallsResponse},
- {RIL_REQUEST_DIAL, radio_1_6::dialResponse},
- {RIL_REQUEST_GET_IMSI, radio_1_6::getIMSIForAppResponse},
- {RIL_REQUEST_HANGUP, radio_1_6::hangupConnectionResponse},
- {RIL_REQUEST_HANGUP_WAITING_OR_BACKGROUND, radio_1_6::hangupWaitingOrBackgroundResponse},
- {RIL_REQUEST_HANGUP_FOREGROUND_RESUME_BACKGROUND, radio_1_6::hangupForegroundResumeBackgroundResponse},
- {RIL_REQUEST_SWITCH_WAITING_OR_HOLDING_AND_ACTIVE, radio_1_6::switchWaitingOrHoldingAndActiveResponse},
- {RIL_REQUEST_CONFERENCE, radio_1_6::conferenceResponse},
- {RIL_REQUEST_UDUB, radio_1_6::rejectCallResponse},
- {RIL_REQUEST_LAST_CALL_FAIL_CAUSE, radio_1_6::getLastCallFailCauseResponse},
- {RIL_REQUEST_SIGNAL_STRENGTH, radio_1_6::getSignalStrengthResponse},
- {RIL_REQUEST_VOICE_REGISTRATION_STATE, radio_1_6::getVoiceRegistrationStateResponse},
- {RIL_REQUEST_DATA_REGISTRATION_STATE, radio_1_6::getDataRegistrationStateResponse},
- {RIL_REQUEST_OPERATOR, radio_1_6::getOperatorResponse},
- {RIL_REQUEST_RADIO_POWER, radio_1_6::setRadioPowerResponse},
- {RIL_REQUEST_DTMF, radio_1_6::sendDtmfResponse},
- {RIL_REQUEST_SEND_SMS, radio_1_6::sendSmsResponse},
- {RIL_REQUEST_SEND_SMS_EXPECT_MORE, radio_1_6::sendSmsExpectMoreResponse},
- {RIL_REQUEST_SETUP_DATA_CALL, radio_1_6::setupDataCallResponse},
- {RIL_REQUEST_SIM_IO, radio_1_6::iccIOForAppResponse},
- {RIL_REQUEST_SEND_USSD, radio_1_6::sendUssdResponse},
- {RIL_REQUEST_CANCEL_USSD, radio_1_6::cancelPendingUssdResponse},
- {RIL_REQUEST_GET_CLIR, radio_1_6::getClirResponse},
- {RIL_REQUEST_SET_CLIR, radio_1_6::setClirResponse},
- {RIL_REQUEST_QUERY_CALL_FORWARD_STATUS, radio_1_6::getCallForwardStatusResponse},
- {RIL_REQUEST_SET_CALL_FORWARD, radio_1_6::setCallForwardResponse},
- {RIL_REQUEST_QUERY_CALL_WAITING, radio_1_6::getCallWaitingResponse},
- {RIL_REQUEST_SET_CALL_WAITING, radio_1_6::setCallWaitingResponse},
- {RIL_REQUEST_SMS_ACKNOWLEDGE, radio_1_6::acknowledgeLastIncomingGsmSmsResponse},
- {RIL_REQUEST_GET_IMEI, NULL},
- {RIL_REQUEST_GET_IMEISV, NULL},
- {RIL_REQUEST_ANSWER, radio_1_6::acceptCallResponse},
- {RIL_REQUEST_DEACTIVATE_DATA_CALL, radio_1_6::deactivateDataCallResponse},
- {RIL_REQUEST_QUERY_FACILITY_LOCK, radio_1_6::getFacilityLockForAppResponse},
- {RIL_REQUEST_SET_FACILITY_LOCK, radio_1_6::setFacilityLockForAppResponse},
- {RIL_REQUEST_CHANGE_BARRING_PASSWORD, radio_1_6::setBarringPasswordResponse},
- {RIL_REQUEST_QUERY_NETWORK_SELECTION_MODE, radio_1_6::getNetworkSelectionModeResponse},
- {RIL_REQUEST_SET_NETWORK_SELECTION_AUTOMATIC, radio_1_6::setNetworkSelectionModeAutomaticResponse},
- {RIL_REQUEST_SET_NETWORK_SELECTION_MANUAL, radio_1_6::setNetworkSelectionModeManualResponse},
- {RIL_REQUEST_QUERY_AVAILABLE_NETWORKS , radio_1_6::getAvailableNetworksResponse},
- {RIL_REQUEST_DTMF_START, radio_1_6::startDtmfResponse},
- {RIL_REQUEST_DTMF_STOP, radio_1_6::stopDtmfResponse},
- {RIL_REQUEST_BASEBAND_VERSION, radio_1_6::getBasebandVersionResponse},
- {RIL_REQUEST_SEPARATE_CONNECTION, radio_1_6::separateConnectionResponse},
- {RIL_REQUEST_SET_MUTE, radio_1_6::setMuteResponse},
- {RIL_REQUEST_GET_MUTE, radio_1_6::getMuteResponse},
- {RIL_REQUEST_QUERY_CLIP, radio_1_6::getClipResponse},
- {RIL_REQUEST_LAST_DATA_CALL_FAIL_CAUSE, NULL},
- {RIL_REQUEST_DATA_CALL_LIST, radio_1_6::getDataCallListResponse},
- {RIL_REQUEST_RESET_RADIO, NULL},
- {RIL_REQUEST_OEM_HOOK_RAW, radio_1_6::sendRequestRawResponse},
- {RIL_REQUEST_OEM_HOOK_STRINGS, radio_1_6::sendRequestStringsResponse},
- {RIL_REQUEST_SCREEN_STATE, radio_1_6::sendDeviceStateResponse}, // Note the response function is different.
- {RIL_REQUEST_SET_SUPP_SVC_NOTIFICATION, radio_1_6::setSuppServiceNotificationsResponse},
- {RIL_REQUEST_WRITE_SMS_TO_SIM, radio_1_6::writeSmsToSimResponse},
- {RIL_REQUEST_DELETE_SMS_ON_SIM, radio_1_6::deleteSmsOnSimResponse},
- {RIL_REQUEST_SET_BAND_MODE, radio_1_6::setBandModeResponse},
- {RIL_REQUEST_QUERY_AVAILABLE_BAND_MODE, radio_1_6::getAvailableBandModesResponse},
- {RIL_REQUEST_STK_GET_PROFILE, NULL},
- {RIL_REQUEST_STK_SET_PROFILE, NULL},
- {RIL_REQUEST_STK_SEND_ENVELOPE_COMMAND, radio_1_6::sendEnvelopeResponse},
- {RIL_REQUEST_STK_SEND_TERMINAL_RESPONSE, radio_1_6::sendTerminalResponseToSimResponse},
- {RIL_REQUEST_STK_HANDLE_CALL_SETUP_REQUESTED_FROM_SIM, radio_1_6::handleStkCallSetupRequestFromSimResponse},
- {RIL_REQUEST_EXPLICIT_CALL_TRANSFER, radio_1_6::explicitCallTransferResponse},
- {RIL_REQUEST_SET_PREFERRED_NETWORK_TYPE, radio_1_6::setPreferredNetworkTypeResponse},
- {RIL_REQUEST_GET_PREFERRED_NETWORK_TYPE, radio_1_6::getPreferredNetworkTypeResponse},
- {RIL_REQUEST_GET_NEIGHBORING_CELL_IDS, radio_1_6::getNeighboringCidsResponse},
- {RIL_REQUEST_SET_LOCATION_UPDATES, radio_1_6::setLocationUpdatesResponse},
- {RIL_REQUEST_CDMA_SET_SUBSCRIPTION_SOURCE, radio_1_6::setCdmaSubscriptionSourceResponse},
- {RIL_REQUEST_CDMA_SET_ROAMING_PREFERENCE, radio_1_6::setCdmaRoamingPreferenceResponse},
- {RIL_REQUEST_CDMA_QUERY_ROAMING_PREFERENCE, radio_1_6::getCdmaRoamingPreferenceResponse},
- {RIL_REQUEST_SET_TTY_MODE, radio_1_6::setTTYModeResponse},
- {RIL_REQUEST_QUERY_TTY_MODE, radio_1_6::getTTYModeResponse},
- {RIL_REQUEST_CDMA_SET_PREFERRED_VOICE_PRIVACY_MODE, radio_1_6::setPreferredVoicePrivacyResponse},
- {RIL_REQUEST_CDMA_QUERY_PREFERRED_VOICE_PRIVACY_MODE, radio_1_6::getPreferredVoicePrivacyResponse},
- {RIL_REQUEST_CDMA_FLASH, radio_1_6::sendCDMAFeatureCodeResponse},
- {RIL_REQUEST_CDMA_BURST_DTMF, radio_1_6::sendBurstDtmfResponse},
- {RIL_REQUEST_CDMA_VALIDATE_AND_WRITE_AKEY, NULL},
- {RIL_REQUEST_CDMA_SEND_SMS, radio_1_6::sendCdmaSmsResponse},
- {RIL_REQUEST_CDMA_SMS_ACKNOWLEDGE, radio_1_6::acknowledgeLastIncomingCdmaSmsResponse},
- {RIL_REQUEST_GSM_GET_BROADCAST_SMS_CONFIG, radio_1_6::getGsmBroadcastConfigResponse},
- {RIL_REQUEST_GSM_SET_BROADCAST_SMS_CONFIG, radio_1_6::setGsmBroadcastConfigResponse},
- {RIL_REQUEST_GSM_SMS_BROADCAST_ACTIVATION, radio_1_6::setGsmBroadcastActivationResponse},
- {RIL_REQUEST_CDMA_GET_BROADCAST_SMS_CONFIG, radio_1_6::getCdmaBroadcastConfigResponse},
- {RIL_REQUEST_CDMA_SET_BROADCAST_SMS_CONFIG, radio_1_6::setCdmaBroadcastConfigResponse},
- {RIL_REQUEST_CDMA_SMS_BROADCAST_ACTIVATION, radio_1_6::setCdmaBroadcastActivationResponse},
- {RIL_REQUEST_CDMA_SUBSCRIPTION, radio_1_6::getCDMASubscriptionResponse},
- {RIL_REQUEST_CDMA_WRITE_SMS_TO_RUIM, radio_1_6::writeSmsToRuimResponse},
- {RIL_REQUEST_CDMA_DELETE_SMS_ON_RUIM, radio_1_6::deleteSmsOnRuimResponse},
- {RIL_REQUEST_DEVICE_IDENTITY, radio_1_6::getDeviceIdentityResponse},
- {RIL_REQUEST_EXIT_EMERGENCY_CALLBACK_MODE, radio_1_6::exitEmergencyCallbackModeResponse},
- {RIL_REQUEST_GET_SMSC_ADDRESS, radio_1_6::getSmscAddressResponse},
- {RIL_REQUEST_SET_SMSC_ADDRESS, radio_1_6::setSmscAddressResponse},
- {RIL_REQUEST_REPORT_SMS_MEMORY_STATUS, radio_1_6::reportSmsMemoryStatusResponse},
- {RIL_REQUEST_REPORT_STK_SERVICE_IS_RUNNING, radio_1_6::reportStkServiceIsRunningResponse},
- {RIL_REQUEST_CDMA_GET_SUBSCRIPTION_SOURCE, radio_1_6::getCdmaSubscriptionSourceResponse},
- {RIL_REQUEST_ISIM_AUTHENTICATION, radio_1_6::requestIsimAuthenticationResponse},
- {RIL_REQUEST_ACKNOWLEDGE_INCOMING_GSM_SMS_WITH_PDU, radio_1_6::acknowledgeIncomingGsmSmsWithPduResponse},
- {RIL_REQUEST_STK_SEND_ENVELOPE_WITH_STATUS, radio_1_6::sendEnvelopeWithStatusResponse},
- {RIL_REQUEST_VOICE_RADIO_TECH, radio_1_6::getVoiceRadioTechnologyResponse},
- {RIL_REQUEST_GET_CELL_INFO_LIST, radio_1_6::getCellInfoListResponse},
- {RIL_REQUEST_SET_UNSOL_CELL_INFO_LIST_RATE, radio_1_6::setCellInfoListRateResponse},
- {RIL_REQUEST_SET_INITIAL_ATTACH_APN, radio_1_6::setInitialAttachApnResponse},
- {RIL_REQUEST_IMS_REGISTRATION_STATE, radio_1_6::getImsRegistrationStateResponse},
- {RIL_REQUEST_IMS_SEND_SMS, radio_1_6::sendImsSmsResponse},
- {RIL_REQUEST_SIM_TRANSMIT_APDU_BASIC, radio_1_6::iccTransmitApduBasicChannelResponse},
- {RIL_REQUEST_SIM_OPEN_CHANNEL, radio_1_6::iccOpenLogicalChannelResponse},
- {RIL_REQUEST_SIM_CLOSE_CHANNEL, radio_1_6::iccCloseLogicalChannelResponse},
- {RIL_REQUEST_SIM_TRANSMIT_APDU_CHANNEL, radio_1_6::iccTransmitApduLogicalChannelResponse},
- {RIL_REQUEST_NV_READ_ITEM, radio_1_6::nvReadItemResponse},
- {RIL_REQUEST_NV_WRITE_ITEM, radio_1_6::nvWriteItemResponse},
- {RIL_REQUEST_NV_WRITE_CDMA_PRL, radio_1_6::nvWriteCdmaPrlResponse},
- {RIL_REQUEST_NV_RESET_CONFIG, radio_1_6::nvResetConfigResponse},
- {RIL_REQUEST_SET_UICC_SUBSCRIPTION, radio_1_6::setUiccSubscriptionResponse},
- {RIL_REQUEST_ALLOW_DATA, radio_1_6::setDataAllowedResponse},
- {RIL_REQUEST_GET_HARDWARE_CONFIG, radio_1_6::getHardwareConfigResponse},
- {RIL_REQUEST_SIM_AUTHENTICATION, radio_1_6::requestIccSimAuthenticationResponse},
- {RIL_REQUEST_GET_DC_RT_INFO, NULL},
- {RIL_REQUEST_SET_DC_RT_INFO_RATE, NULL},
- {RIL_REQUEST_SET_DATA_PROFILE, radio_1_6::setDataProfileResponse},
- {RIL_REQUEST_SHUTDOWN, radio_1_6::requestShutdownResponse},
- {RIL_REQUEST_GET_RADIO_CAPABILITY, radio_1_6::getRadioCapabilityResponse},
- {RIL_REQUEST_SET_RADIO_CAPABILITY, radio_1_6::setRadioCapabilityResponse},
- {RIL_REQUEST_START_LCE, radio_1_6::startLceServiceResponse},
- {RIL_REQUEST_STOP_LCE, radio_1_6::stopLceServiceResponse},
- {RIL_REQUEST_PULL_LCEDATA, radio_1_6::pullLceDataResponse},
- {RIL_REQUEST_GET_ACTIVITY_INFO, radio_1_6::getModemActivityInfoResponse},
- {RIL_REQUEST_SET_CARRIER_RESTRICTIONS, radio_1_6::setAllowedCarriersResponse},
- {RIL_REQUEST_GET_CARRIER_RESTRICTIONS, radio_1_6::getAllowedCarriersResponse},
- {RIL_REQUEST_SEND_DEVICE_STATE, radio_1_6::sendDeviceStateResponse},
- {RIL_REQUEST_SET_UNSOLICITED_RESPONSE_FILTER, radio_1_6::setIndicationFilterResponse},
- {RIL_REQUEST_SET_SIM_CARD_POWER, radio_1_6::setSimCardPowerResponse},
- {RIL_REQUEST_SET_CARRIER_INFO_IMSI_ENCRYPTION, radio_1_6::setCarrierInfoForImsiEncryptionResponse},
- {RIL_REQUEST_START_NETWORK_SCAN, radio_1_6::startNetworkScanResponse},
- {RIL_REQUEST_STOP_NETWORK_SCAN, radio_1_6::stopNetworkScanResponse},
- {RIL_REQUEST_START_KEEPALIVE, radio_1_6::startKeepaliveResponse},
- {RIL_REQUEST_STOP_KEEPALIVE, radio_1_6::stopKeepaliveResponse},
- {RIL_REQUEST_GET_MODEM_STACK_STATUS, radio_1_6::getModemStackStatusResponse},
- {RIL_REQUEST_GET_PREFERRED_NETWORK_TYPE_BITMAP, radio_1_6::getPreferredNetworkTypeBitmapResponse},
- {RIL_REQUEST_SET_PREFERRED_NETWORK_TYPE_BITMAP, radio_1_6::setPreferredNetworkTypeBitmapResponse},
- {RIL_REQUEST_EMERGENCY_DIAL, radio_1_6::emergencyDialResponse},
- {RIL_REQUEST_SET_SYSTEM_SELECTION_CHANNELS, radio_1_6::setSystemSelectionChannelsResponse},
- {RIL_REQUEST_ENABLE_MODEM, radio_1_6::enableModemResponse},
- {RIL_REQUEST_SET_SIGNAL_STRENGTH_REPORTING_CRITERIA, radio_1_6::setSignalStrengthReportingCriteriaResponse},
- {RIL_REQUEST_SET_LINK_CAPACITY_REPORTING_CRITERIA, radio_1_6::setLinkCapacityReportingCriteriaResponse},
- {RIL_REQUEST_ENABLE_UICC_APPLICATIONS, radio_1_6::enableUiccApplicationsResponse},
- {RIL_REQUEST_ARE_UICC_APPLICATIONS_ENABLED, radio_1_6::areUiccApplicationsEnabledResponse},
- {RIL_REQUEST_ENTER_SIM_DEPERSONALIZATION, radio_1_6::supplySimDepersonalizationResponse},
- {RIL_REQUEST_CDMA_SEND_SMS_EXPECT_MORE, radio_1_6::sendCdmaSmsExpectMoreResponse},
- {RIL_REQUEST_GET_BARRING_INFO, radio_1_6::getBarringInfoResponse},
- {RIL_REQUEST_ENABLE_NR_DUAL_CONNECTIVITY, radio_1_6::setNrDualConnectivityStateResponse},
- {RIL_REQUEST_IS_NR_DUAL_CONNECTIVITY_ENABLED, radio_1_6::isNrDualConnectivityEnabledResponse},
- {RIL_REQUEST_ALLOCATE_PDU_SESSION_ID, radio_1_6::allocatePduSessionIdResponse},
- {RIL_REQUEST_RELEASE_PDU_SESSION_ID, radio_1_6::releasePduSessionIdResponse},
- {RIL_REQUEST_START_HANDOVER, radio_1_6::startHandoverResponse},
- {RIL_REQUEST_CANCEL_HANDOVER, radio_1_6::cancelHandoverResponse},
- {RIL_REQUEST_SET_ALLOWED_NETWORK_TYPES_BITMAP, radio_1_6::setAllowedNetworkTypesBitmapResponse},
- {RIL_REQUEST_SET_DATA_THROTTLING, radio_1_6::setDataThrottlingResponse},
- {RIL_REQUEST_GET_SYSTEM_SELECTION_CHANNELS, radio_1_6::getSystemSelectionChannelsResponse},
- {RIL_REQUEST_GET_ALLOWED_NETWORK_TYPES_BITMAP, radio_1_6::getAllowedNetworkTypesBitmapResponse},
- {RIL_REQUEST_GET_SLICING_CONFIG, radio_1_6::getSlicingConfigResponse},
- {RIL_REQUEST_GET_SIM_PHONEBOOK_RECORDS, radio_1_6::getSimPhonebookRecordsResponse},
- {RIL_REQUEST_GET_SIM_PHONEBOOK_CAPACITY, radio_1_6::getSimPhonebookCapacityResponse},
- {RIL_REQUEST_UPDATE_SIM_PHONEBOOK_RECORDS, radio_1_6::updateSimPhonebookRecordsResponse}
+{0, NULL}, // none
+ {RIL_REQUEST_GET_SIM_STATUS, radio_1_6::getIccCardStatusResponse},
+ {RIL_REQUEST_ENTER_SIM_PIN, radio_1_6::supplyIccPinForAppResponse},
+ {RIL_REQUEST_ENTER_SIM_PUK, radio_1_6::supplyIccPukForAppResponse},
+ {RIL_REQUEST_ENTER_SIM_PIN2, radio_1_6::supplyIccPin2ForAppResponse},
+ {RIL_REQUEST_ENTER_SIM_PUK2, radio_1_6::supplyIccPuk2ForAppResponse},
+ {RIL_REQUEST_CHANGE_SIM_PIN, radio_1_6::changeIccPinForAppResponse},
+ {RIL_REQUEST_CHANGE_SIM_PIN2, radio_1_6::changeIccPin2ForAppResponse},
+ {RIL_REQUEST_ENTER_NETWORK_DEPERSONALIZATION,
+ radio_1_6::supplyNetworkDepersonalizationResponse},
+ {RIL_REQUEST_GET_CURRENT_CALLS, radio_1_6::getCurrentCallsResponse},
+ {RIL_REQUEST_DIAL, radio_1_6::dialResponse},
+ {RIL_REQUEST_GET_IMSI, radio_1_6::getIMSIForAppResponse},
+ {RIL_REQUEST_HANGUP, radio_1_6::hangupConnectionResponse},
+ {RIL_REQUEST_HANGUP_WAITING_OR_BACKGROUND, radio_1_6::hangupWaitingOrBackgroundResponse},
+ {RIL_REQUEST_HANGUP_FOREGROUND_RESUME_BACKGROUND,
+ radio_1_6::hangupForegroundResumeBackgroundResponse},
+ {RIL_REQUEST_SWITCH_WAITING_OR_HOLDING_AND_ACTIVE,
+ radio_1_6::switchWaitingOrHoldingAndActiveResponse},
+ {RIL_REQUEST_CONFERENCE, radio_1_6::conferenceResponse},
+ {RIL_REQUEST_UDUB, radio_1_6::rejectCallResponse},
+ {RIL_REQUEST_LAST_CALL_FAIL_CAUSE, radio_1_6::getLastCallFailCauseResponse},
+ {RIL_REQUEST_SIGNAL_STRENGTH, radio_1_6::getSignalStrengthResponse},
+ {RIL_REQUEST_VOICE_REGISTRATION_STATE, radio_1_6::getVoiceRegistrationStateResponse},
+ {RIL_REQUEST_DATA_REGISTRATION_STATE, radio_1_6::getDataRegistrationStateResponse},
+ {RIL_REQUEST_OPERATOR, radio_1_6::getOperatorResponse},
+ {RIL_REQUEST_RADIO_POWER, radio_1_6::setRadioPowerResponse},
+ {RIL_REQUEST_DTMF, radio_1_6::sendDtmfResponse},
+ {RIL_REQUEST_SEND_SMS, radio_1_6::sendSmsResponse},
+ {RIL_REQUEST_SEND_SMS_EXPECT_MORE, radio_1_6::sendSmsExpectMoreResponse},
+ {RIL_REQUEST_SETUP_DATA_CALL, radio_1_6::setupDataCallResponse},
+ {RIL_REQUEST_SIM_IO, radio_1_6::iccIOForAppResponse},
+ {RIL_REQUEST_SEND_USSD, radio_1_6::sendUssdResponse},
+ {RIL_REQUEST_CANCEL_USSD, radio_1_6::cancelPendingUssdResponse},
+ {RIL_REQUEST_GET_CLIR, radio_1_6::getClirResponse},
+ {RIL_REQUEST_SET_CLIR, radio_1_6::setClirResponse},
+ {RIL_REQUEST_QUERY_CALL_FORWARD_STATUS, radio_1_6::getCallForwardStatusResponse},
+ {RIL_REQUEST_SET_CALL_FORWARD, radio_1_6::setCallForwardResponse},
+ {RIL_REQUEST_QUERY_CALL_WAITING, radio_1_6::getCallWaitingResponse},
+ {RIL_REQUEST_SET_CALL_WAITING, radio_1_6::setCallWaitingResponse},
+ {RIL_REQUEST_SMS_ACKNOWLEDGE, radio_1_6::acknowledgeLastIncomingGsmSmsResponse},
+ {RIL_REQUEST_GET_IMEI, NULL}, {RIL_REQUEST_GET_IMEISV, NULL},
+ {RIL_REQUEST_ANSWER, radio_1_6::acceptCallResponse},
+ {RIL_REQUEST_DEACTIVATE_DATA_CALL, radio_1_6::deactivateDataCallResponse},
+ {RIL_REQUEST_QUERY_FACILITY_LOCK, radio_1_6::getFacilityLockForAppResponse},
+ {RIL_REQUEST_SET_FACILITY_LOCK, radio_1_6::setFacilityLockForAppResponse},
+ {RIL_REQUEST_CHANGE_BARRING_PASSWORD, radio_1_6::setBarringPasswordResponse},
+ {RIL_REQUEST_QUERY_NETWORK_SELECTION_MODE, radio_1_6::getNetworkSelectionModeResponse},
+ {RIL_REQUEST_SET_NETWORK_SELECTION_AUTOMATIC,
+ radio_1_6::setNetworkSelectionModeAutomaticResponse},
+ {RIL_REQUEST_SET_NETWORK_SELECTION_MANUAL,
+ radio_1_6::setNetworkSelectionModeManualResponse},
+ {RIL_REQUEST_QUERY_AVAILABLE_NETWORKS, radio_1_6::getAvailableNetworksResponse},
+ {RIL_REQUEST_DTMF_START, radio_1_6::startDtmfResponse},
+ {RIL_REQUEST_DTMF_STOP, radio_1_6::stopDtmfResponse},
+ {RIL_REQUEST_BASEBAND_VERSION, radio_1_6::getBasebandVersionResponse},
+ {RIL_REQUEST_SEPARATE_CONNECTION, radio_1_6::separateConnectionResponse},
+ {RIL_REQUEST_SET_MUTE, radio_1_6::setMuteResponse},
+ {RIL_REQUEST_GET_MUTE, radio_1_6::getMuteResponse},
+ {RIL_REQUEST_QUERY_CLIP, radio_1_6::getClipResponse},
+ {RIL_REQUEST_LAST_DATA_CALL_FAIL_CAUSE, NULL},
+ {RIL_REQUEST_DATA_CALL_LIST, radio_1_6::getDataCallListResponse},
+ {RIL_REQUEST_RESET_RADIO, NULL},
+ {RIL_REQUEST_OEM_HOOK_RAW, radio_1_6::sendRequestRawResponse},
+ {RIL_REQUEST_OEM_HOOK_STRINGS, radio_1_6::sendRequestStringsResponse},
+ {RIL_REQUEST_SCREEN_STATE,
+ radio_1_6::sendDeviceStateResponse}, // Note the response function is different.
+ {RIL_REQUEST_SET_SUPP_SVC_NOTIFICATION, radio_1_6::setSuppServiceNotificationsResponse},
+ {RIL_REQUEST_WRITE_SMS_TO_SIM, radio_1_6::writeSmsToSimResponse},
+ {RIL_REQUEST_DELETE_SMS_ON_SIM, radio_1_6::deleteSmsOnSimResponse},
+ {RIL_REQUEST_SET_BAND_MODE, radio_1_6::setBandModeResponse},
+ {RIL_REQUEST_QUERY_AVAILABLE_BAND_MODE, radio_1_6::getAvailableBandModesResponse},
+ {RIL_REQUEST_STK_GET_PROFILE, NULL}, {RIL_REQUEST_STK_SET_PROFILE, NULL},
+ {RIL_REQUEST_STK_SEND_ENVELOPE_COMMAND, radio_1_6::sendEnvelopeResponse},
+ {RIL_REQUEST_STK_SEND_TERMINAL_RESPONSE, radio_1_6::sendTerminalResponseToSimResponse},
+ {RIL_REQUEST_STK_HANDLE_CALL_SETUP_REQUESTED_FROM_SIM,
+ radio_1_6::handleStkCallSetupRequestFromSimResponse},
+ {RIL_REQUEST_EXPLICIT_CALL_TRANSFER, radio_1_6::explicitCallTransferResponse},
+ {RIL_REQUEST_SET_PREFERRED_NETWORK_TYPE, radio_1_6::setPreferredNetworkTypeResponse},
+ {RIL_REQUEST_GET_PREFERRED_NETWORK_TYPE, radio_1_6::getPreferredNetworkTypeResponse},
+ {RIL_REQUEST_GET_NEIGHBORING_CELL_IDS, radio_1_6::getNeighboringCidsResponse},
+ {RIL_REQUEST_SET_LOCATION_UPDATES, radio_1_6::setLocationUpdatesResponse},
+ {RIL_REQUEST_CDMA_SET_SUBSCRIPTION_SOURCE, radio_1_6::setCdmaSubscriptionSourceResponse},
+ {RIL_REQUEST_CDMA_SET_ROAMING_PREFERENCE, radio_1_6::setCdmaRoamingPreferenceResponse},
+ {RIL_REQUEST_CDMA_QUERY_ROAMING_PREFERENCE, radio_1_6::getCdmaRoamingPreferenceResponse},
+ {RIL_REQUEST_SET_TTY_MODE, radio_1_6::setTTYModeResponse},
+ {RIL_REQUEST_QUERY_TTY_MODE, radio_1_6::getTTYModeResponse},
+ {RIL_REQUEST_CDMA_SET_PREFERRED_VOICE_PRIVACY_MODE,
+ radio_1_6::setPreferredVoicePrivacyResponse},
+ {RIL_REQUEST_CDMA_QUERY_PREFERRED_VOICE_PRIVACY_MODE,
+ radio_1_6::getPreferredVoicePrivacyResponse},
+ {RIL_REQUEST_CDMA_FLASH, radio_1_6::sendCDMAFeatureCodeResponse},
+ {RIL_REQUEST_CDMA_BURST_DTMF, radio_1_6::sendBurstDtmfResponse},
+ {RIL_REQUEST_CDMA_VALIDATE_AND_WRITE_AKEY, NULL},
+ {RIL_REQUEST_CDMA_SEND_SMS, radio_1_6::sendCdmaSmsResponse},
+ {RIL_REQUEST_CDMA_SMS_ACKNOWLEDGE, radio_1_6::acknowledgeLastIncomingCdmaSmsResponse},
+ {RIL_REQUEST_GSM_GET_BROADCAST_SMS_CONFIG, radio_1_6::getGsmBroadcastConfigResponse},
+ {RIL_REQUEST_GSM_SET_BROADCAST_SMS_CONFIG, radio_1_6::setGsmBroadcastConfigResponse},
+ {RIL_REQUEST_GSM_SMS_BROADCAST_ACTIVATION, radio_1_6::setGsmBroadcastActivationResponse},
+ {RIL_REQUEST_CDMA_GET_BROADCAST_SMS_CONFIG, radio_1_6::getCdmaBroadcastConfigResponse},
+ {RIL_REQUEST_CDMA_SET_BROADCAST_SMS_CONFIG, radio_1_6::setCdmaBroadcastConfigResponse},
+ {RIL_REQUEST_CDMA_SMS_BROADCAST_ACTIVATION, radio_1_6::setCdmaBroadcastActivationResponse},
+ {RIL_REQUEST_CDMA_SUBSCRIPTION, radio_1_6::getCDMASubscriptionResponse},
+ {RIL_REQUEST_CDMA_WRITE_SMS_TO_RUIM, radio_1_6::writeSmsToRuimResponse},
+ {RIL_REQUEST_CDMA_DELETE_SMS_ON_RUIM, radio_1_6::deleteSmsOnRuimResponse},
+ {RIL_REQUEST_DEVICE_IDENTITY, radio_1_6::getDeviceIdentityResponse},
+ {RIL_REQUEST_EXIT_EMERGENCY_CALLBACK_MODE, radio_1_6::exitEmergencyCallbackModeResponse},
+ {RIL_REQUEST_GET_SMSC_ADDRESS, radio_1_6::getSmscAddressResponse},
+ {RIL_REQUEST_SET_SMSC_ADDRESS, radio_1_6::setSmscAddressResponse},
+ {RIL_REQUEST_REPORT_SMS_MEMORY_STATUS, radio_1_6::reportSmsMemoryStatusResponse},
+ {RIL_REQUEST_REPORT_STK_SERVICE_IS_RUNNING, radio_1_6::reportStkServiceIsRunningResponse},
+ {RIL_REQUEST_CDMA_GET_SUBSCRIPTION_SOURCE, radio_1_6::getCdmaSubscriptionSourceResponse},
+ {RIL_REQUEST_ISIM_AUTHENTICATION, radio_1_6::requestIsimAuthenticationResponse},
+ {RIL_REQUEST_ACKNOWLEDGE_INCOMING_GSM_SMS_WITH_PDU,
+ radio_1_6::acknowledgeIncomingGsmSmsWithPduResponse},
+ {RIL_REQUEST_STK_SEND_ENVELOPE_WITH_STATUS, radio_1_6::sendEnvelopeWithStatusResponse},
+ {RIL_REQUEST_VOICE_RADIO_TECH, radio_1_6::getVoiceRadioTechnologyResponse},
+ {RIL_REQUEST_GET_CELL_INFO_LIST, radio_1_6::getCellInfoListResponse},
+ {RIL_REQUEST_SET_UNSOL_CELL_INFO_LIST_RATE, radio_1_6::setCellInfoListRateResponse},
+ {RIL_REQUEST_SET_INITIAL_ATTACH_APN, radio_1_6::setInitialAttachApnResponse},
+ {RIL_REQUEST_IMS_REGISTRATION_STATE, radio_1_6::getImsRegistrationStateResponse},
+ {RIL_REQUEST_IMS_SEND_SMS, radio_1_6::sendImsSmsResponse},
+ {RIL_REQUEST_SIM_TRANSMIT_APDU_BASIC, radio_1_6::iccTransmitApduBasicChannelResponse},
+ {RIL_REQUEST_SIM_OPEN_CHANNEL, radio_1_6::iccOpenLogicalChannelResponse},
+ {RIL_REQUEST_SIM_CLOSE_CHANNEL, radio_1_6::iccCloseLogicalChannelResponse},
+ {RIL_REQUEST_SIM_TRANSMIT_APDU_CHANNEL, radio_1_6::iccTransmitApduLogicalChannelResponse},
+ {RIL_REQUEST_NV_READ_ITEM, radio_1_6::nvReadItemResponse},
+ {RIL_REQUEST_NV_WRITE_ITEM, radio_1_6::nvWriteItemResponse},
+ {RIL_REQUEST_NV_WRITE_CDMA_PRL, radio_1_6::nvWriteCdmaPrlResponse},
+ {RIL_REQUEST_NV_RESET_CONFIG, radio_1_6::nvResetConfigResponse},
+ {RIL_REQUEST_SET_UICC_SUBSCRIPTION, radio_1_6::setUiccSubscriptionResponse},
+ {RIL_REQUEST_ALLOW_DATA, radio_1_6::setDataAllowedResponse},
+ {RIL_REQUEST_GET_HARDWARE_CONFIG, radio_1_6::getHardwareConfigResponse},
+ {RIL_REQUEST_SIM_AUTHENTICATION, radio_1_6::requestIccSimAuthenticationResponse},
+ {RIL_REQUEST_GET_DC_RT_INFO, NULL}, {RIL_REQUEST_SET_DC_RT_INFO_RATE, NULL},
+ {RIL_REQUEST_SET_DATA_PROFILE, radio_1_6::setDataProfileResponse},
+ {RIL_REQUEST_SHUTDOWN, radio_1_6::requestShutdownResponse},
+ {RIL_REQUEST_GET_RADIO_CAPABILITY, radio_1_6::getRadioCapabilityResponse},
+ {RIL_REQUEST_SET_RADIO_CAPABILITY, radio_1_6::setRadioCapabilityResponse},
+ {RIL_REQUEST_START_LCE, radio_1_6::startLceServiceResponse},
+ {RIL_REQUEST_STOP_LCE, radio_1_6::stopLceServiceResponse},
+ {RIL_REQUEST_PULL_LCEDATA, radio_1_6::pullLceDataResponse},
+ {RIL_REQUEST_GET_ACTIVITY_INFO, radio_1_6::getModemActivityInfoResponse},
+ {RIL_REQUEST_SET_CARRIER_RESTRICTIONS, radio_1_6::setAllowedCarriersResponse},
+ {RIL_REQUEST_GET_CARRIER_RESTRICTIONS, radio_1_6::getAllowedCarriersResponse},
+ {RIL_REQUEST_SEND_DEVICE_STATE, radio_1_6::sendDeviceStateResponse},
+ {RIL_REQUEST_SET_UNSOLICITED_RESPONSE_FILTER, radio_1_6::setIndicationFilterResponse},
+ {RIL_REQUEST_SET_SIM_CARD_POWER, radio_1_6::setSimCardPowerResponse},
+ {RIL_REQUEST_SET_CARRIER_INFO_IMSI_ENCRYPTION,
+ radio_1_6::setCarrierInfoForImsiEncryptionResponse},
+ {RIL_REQUEST_START_NETWORK_SCAN, radio_1_6::startNetworkScanResponse},
+ {RIL_REQUEST_STOP_NETWORK_SCAN, radio_1_6::stopNetworkScanResponse},
+ {RIL_REQUEST_START_KEEPALIVE, radio_1_6::startKeepaliveResponse},
+ {RIL_REQUEST_STOP_KEEPALIVE, radio_1_6::stopKeepaliveResponse},
+ {RIL_REQUEST_GET_MODEM_STACK_STATUS, radio_1_6::getModemStackStatusResponse},
+ {RIL_REQUEST_GET_PREFERRED_NETWORK_TYPE_BITMAP,
+ radio_1_6::getPreferredNetworkTypeBitmapResponse},
+ {RIL_REQUEST_SET_PREFERRED_NETWORK_TYPE_BITMAP,
+ radio_1_6::setPreferredNetworkTypeBitmapResponse},
+ {RIL_REQUEST_EMERGENCY_DIAL, radio_1_6::emergencyDialResponse},
+ {RIL_REQUEST_SET_SYSTEM_SELECTION_CHANNELS, radio_1_6::setSystemSelectionChannelsResponse},
+ {RIL_REQUEST_ENABLE_MODEM, radio_1_6::enableModemResponse},
+ {RIL_REQUEST_SET_SIGNAL_STRENGTH_REPORTING_CRITERIA,
+ radio_1_6::setSignalStrengthReportingCriteriaResponse},
+ {RIL_REQUEST_SET_LINK_CAPACITY_REPORTING_CRITERIA,
+ radio_1_6::setLinkCapacityReportingCriteriaResponse},
+ {RIL_REQUEST_ENABLE_UICC_APPLICATIONS, radio_1_6::enableUiccApplicationsResponse},
+ {RIL_REQUEST_ARE_UICC_APPLICATIONS_ENABLED, radio_1_6::areUiccApplicationsEnabledResponse},
+ {RIL_REQUEST_ENTER_SIM_DEPERSONALIZATION, radio_1_6::supplySimDepersonalizationResponse},
+ {RIL_REQUEST_CDMA_SEND_SMS_EXPECT_MORE, radio_1_6::sendCdmaSmsExpectMoreResponse},
+ {RIL_REQUEST_GET_BARRING_INFO, radio_1_6::getBarringInfoResponse},
+ {RIL_REQUEST_ENABLE_NR_DUAL_CONNECTIVITY, radio_1_6::setNrDualConnectivityStateResponse},
+ {RIL_REQUEST_IS_NR_DUAL_CONNECTIVITY_ENABLED,
+ radio_1_6::isNrDualConnectivityEnabledResponse},
+ {RIL_REQUEST_ALLOCATE_PDU_SESSION_ID, radio_1_6::allocatePduSessionIdResponse},
+ {RIL_REQUEST_RELEASE_PDU_SESSION_ID, radio_1_6::releasePduSessionIdResponse},
+ {RIL_REQUEST_START_HANDOVER, radio_1_6::startHandoverResponse},
+ {RIL_REQUEST_CANCEL_HANDOVER, radio_1_6::cancelHandoverResponse},
+ {RIL_REQUEST_SET_ALLOWED_NETWORK_TYPES_BITMAP,
+ radio_1_6::setAllowedNetworkTypesBitmapResponse},
+ {RIL_REQUEST_SET_DATA_THROTTLING, radio_1_6::setDataThrottlingResponse},
+ {RIL_REQUEST_GET_SYSTEM_SELECTION_CHANNELS, radio_1_6::getSystemSelectionChannelsResponse},
+ {RIL_REQUEST_GET_ALLOWED_NETWORK_TYPES_BITMAP,
+ radio_1_6::getAllowedNetworkTypesBitmapResponse},
+ {RIL_REQUEST_GET_SLICING_CONFIG, radio_1_6::getSlicingConfigResponse},
+ {RIL_REQUEST_GET_SIM_PHONEBOOK_RECORDS, radio_1_6::getSimPhonebookRecordsResponse},
+ {RIL_REQUEST_GET_SIM_PHONEBOOK_CAPACITY, radio_1_6::getSimPhonebookCapacityResponse},
+ {RIL_REQUEST_UPDATE_SIM_PHONEBOOK_RECORDS, radio_1_6::updateSimPhonebookRecordsResponse},
+ {RIL_REQUEST_GET_CELL_INFO_LIST_1_6, radio_1_6::getCellInfoListResponse
+}
diff --git a/guest/hals/ril/reference-libril/ril_config.cpp b/guest/hals/ril/reference-libril/ril_config.cpp
index 5a72db9..80c8aa0 100644
--- a/guest/hals/ril/reference-libril/ril_config.cpp
+++ b/guest/hals/ril/reference-libril/ril_config.cpp
@@ -17,12 +17,15 @@
#define LOG_TAG "RILC"
+#include <android-base/logging.h>
+#include <android/binder_manager.h>
+#include <android/binder_process.h>
#include <android/hardware/radio/config/1.1/IRadioConfig.h>
-#include <android/hardware/radio/config/1.2/IRadioConfigResponse.h>
-#include <android/hardware/radio/config/1.3/IRadioConfigResponse.h>
-#include <android/hardware/radio/config/1.3/IRadioConfig.h>
#include <android/hardware/radio/config/1.2/IRadioConfigIndication.h>
-#include <android/hardware/radio/1.1/types.h>
+#include <android/hardware/radio/config/1.2/IRadioConfigResponse.h>
+#include <android/hardware/radio/config/1.3/IRadioConfig.h>
+#include <android/hardware/radio/config/1.3/IRadioConfigResponse.h>
+#include <libradiocompat/RadioConfig.h>
#include <ril.h>
#include <guest/hals/ril/reference-libril/ril_service.h>
@@ -32,8 +35,6 @@
using namespace android::hardware::radio::config;
using namespace android::hardware::radio::config::V1_0;
using namespace android::hardware::radio::config::V1_3;
-using ::android::hardware::configureRpcThreadpool;
-using ::android::hardware::joinRpcThreadpool;
using ::android::hardware::Return;
using ::android::hardware::hidl_string;
using ::android::hardware::hidl_vec;
@@ -111,7 +112,7 @@
const ::android::sp<V1_0::IRadioConfigIndication>& radioConfigIndication) {
pthread_rwlock_t *radioServiceRwlockPtr = radio_1_6::getRadioServiceRwlock(RIL_SOCKET_1);
int ret = pthread_rwlock_wrlock(radioServiceRwlockPtr);
- assert(ret == 0);
+ CHECK_EQ(ret, 0);
mRadioConfigResponse = radioConfigResponse;
mRadioConfigIndication = radioConfigIndication;
@@ -141,7 +142,7 @@
mCounterRadioConfig++;
ret = pthread_rwlock_unlock(radioServiceRwlockPtr);
- assert(ret == 0);
+ CHECK_EQ(ret, 0);
return Void();
}
@@ -166,14 +167,14 @@
}
size_t slotNum = slotMap.size();
- if (slotNum > RIL_SOCKET_NUM) {
+ if (slotNum > MAX_LOGICAL_MODEM_NUM) {
RLOGE("setSimSlotsMapping: invalid parameter");
sendErrorResponse(pRI, RIL_E_INVALID_ARGUMENTS);
return Void();
}
for (size_t socket_id = 0; socket_id < slotNum; socket_id++) {
- if (slotMap[socket_id] >= RIL_SOCKET_NUM) {
+ if (slotMap[socket_id] >= MAX_LOGICAL_MODEM_NUM) {
RLOGE("setSimSlotsMapping: invalid parameter[%zu]", socket_id);
sendErrorResponse(pRI, RIL_E_INVALID_ARGUMENTS);
return Void();
@@ -258,6 +259,9 @@
void radio_1_6::registerConfigService(RIL_RadioFunctions *callbacks, CommandInfo *commands) {
using namespace android::hardware;
+ using namespace std::string_literals;
+ namespace compat = android::hardware::radio::compat;
+
RLOGD("Entry %s", __FUNCTION__);
const char *serviceNames = "default";
@@ -268,7 +272,7 @@
pthread_rwlock_t *radioServiceRwlockPtr = getRadioServiceRwlock(0);
int ret = pthread_rwlock_wrlock(radioServiceRwlockPtr);
- assert(ret == 0);
+ CHECK_EQ(ret, 0);
RLOGD("registerConfigService: starting V1_2::IConfigRadio %s", serviceNames);
radioConfigService = new RadioConfigImpl;
@@ -279,10 +283,17 @@
radioConfigService->mRadioConfigResponseV1_2 = NULL;
radioConfigService->mRadioConfigResponseV1_3 = NULL;
radioConfigService->mRadioConfigIndicationV1_2 = NULL;
- android::status_t status = radioConfigService->registerAsService(serviceNames);
- RLOGD("registerConfigService registerService: status %d", status);
+
+ // use a compat shim to convert HIDL interface to AIDL and publish it
+ // PLEASE NOTE this is a temporary solution
+ static auto aidlHal = ndk::SharedRefBase::make<compat::RadioConfig>(radioConfigService);
+ const auto instance = compat::RadioConfig::descriptor + "/"s + std::string(serviceNames);
+ const auto status = AServiceManager_addService(aidlHal->asBinder().get(), instance.c_str());
+ RLOGD("registerConfigService addService: status %d", status);
+ CHECK_EQ(status, STATUS_OK);
+
ret = pthread_rwlock_unlock(radioServiceRwlockPtr);
- assert(ret == 0);
+ CHECK_EQ(ret, 0);
}
void checkReturnStatus(Return<void>& ret) {
@@ -298,11 +309,11 @@
int counter = mCounterRadioConfig;
pthread_rwlock_t *radioServiceRwlockPtr = radio_1_6::getRadioServiceRwlock(0);
int ret = pthread_rwlock_unlock(radioServiceRwlockPtr);
- assert(ret == 0);
+ CHECK_EQ(ret, 0);
// acquire wrlock
ret = pthread_rwlock_wrlock(radioServiceRwlockPtr);
- assert(ret == 0);
+ CHECK_EQ(ret, 0);
// make sure the counter value has not changed
if (counter == mCounterRadioConfig) {
@@ -320,11 +331,11 @@
// release wrlock
ret = pthread_rwlock_unlock(radioServiceRwlockPtr);
- assert(ret == 0);
+ CHECK_EQ(ret, 0);
// Reacquire rdlock
ret = pthread_rwlock_rdlock(radioServiceRwlockPtr);
- assert(ret == 0);
+ CHECK_EQ(ret, 0);
}
}
diff --git a/guest/hals/ril/reference-libril/ril_service.cpp b/guest/hals/ril/reference-libril/ril_service.cpp
index c7b80a6..5fdc3dc 100644
--- a/guest/hals/ril/reference-libril/ril_service.cpp
+++ b/guest/hals/ril/reference-libril/ril_service.cpp
@@ -16,10 +16,21 @@
#define LOG_TAG "RILC"
+#include "RefRadioNetwork.h"
+
+#include <android-base/logging.h>
+#include <android/binder_manager.h>
+#include <android/binder_process.h>
#include <android/hardware/radio/1.6/IRadio.h>
#include <android/hardware/radio/1.6/IRadioIndication.h>
#include <android/hardware/radio/1.6/IRadioResponse.h>
#include <android/hardware/radio/1.6/types.h>
+#include <libradiocompat/CallbackManager.h>
+#include <libradiocompat/RadioData.h>
+#include <libradiocompat/RadioMessaging.h>
+#include <libradiocompat/RadioModem.h>
+#include <libradiocompat/RadioSim.h>
+#include <libradiocompat/RadioVoice.h>
#include <android/hardware/radio/deprecated/1.0/IOemHook.h>
@@ -37,8 +48,8 @@
using namespace android::hardware::radio;
using namespace android::hardware::radio::V1_0;
using namespace android::hardware::radio::deprecated::V1_0;
-using ::android::hardware::configureRpcThreadpool;
-using ::android::hardware::joinRpcThreadpool;
+using namespace std::string_literals;
+namespace compat = android::hardware::radio::compat;
using ::android::hardware::Return;
using ::android::hardware::hidl_bitfield;
using ::android::hardware::hidl_string;
@@ -119,26 +130,46 @@
void convertRilSignalStrengthToHal(void *response, size_t responseLen,
SignalStrength& signalStrength);
-void convertRilDataCallToHal(RIL_Data_Call_Response_v11 *dcResponse,
- SetupDataCallResult& dcResult);
+void convertRilSignalStrengthToHal_1_2(void* response, size_t responseLen,
+ V1_2::SignalStrength& signalStrength);
void convertRilSignalStrengthToHal_1_4(void *response, size_t responseLen,
V1_4::SignalStrength& signalStrength);
-void convertRilDataCallToHal(RIL_Data_Call_Response_v11 *dcResponse,
- ::android::hardware::radio::V1_4::SetupDataCallResult& dcResult);
+void convertRilSignalStrengthToHal_1_6(void* response, size_t responseLen,
+ V1_6::SignalStrength& signalStrength);
-void convertRilDataCallToHal(RIL_Data_Call_Response_v12 *dcResponse,
- ::android::hardware::radio::V1_5::SetupDataCallResult& dcResult);
+void convertRilDataCallToHal(RIL_Data_Call_Response_v11* dcResponse, SetupDataCallResult& dcResult);
-void convertRilDataCallToHal(RIL_Data_Call_Response_v12 *dcResponse,
- ::android::hardware::radio::V1_6::SetupDataCallResult& dcResult);
+void convertRilDataCallToHal(RIL_Data_Call_Response_v11* dcResponse,
+ V1_4::SetupDataCallResult& dcResult);
+
+void convertRilDataCallToHal(RIL_Data_Call_Response_v11* dcResponse,
+ V1_5::SetupDataCallResult& dcResult);
+
+void convertRilDataCallToHal(RIL_Data_Call_Response_v11* dcResponse,
+ V1_6::SetupDataCallResult& dcResult);
void convertRilDataCallListToHal(void *response, size_t responseLen,
hidl_vec<SetupDataCallResult>& dcResultList);
+void convertRilDataCallListToHal_1_4(void* response, size_t responseLen,
+ hidl_vec<V1_4::SetupDataCallResult>& dcResultList);
+
+void convertRilDataCallListToHal_1_5(void* response, size_t responseLen,
+ hidl_vec<V1_5::SetupDataCallResult>& dcResultList);
+
+void convertRilDataCallListToHal_1_6(void* response, size_t responseLen,
+ hidl_vec<V1_6::SetupDataCallResult>& dcResultList);
+
void convertRilCellInfoListToHal(void *response, size_t responseLen, hidl_vec<CellInfo>& records);
void convertRilCellInfoListToHal_1_2(void *response, size_t responseLen, hidl_vec<V1_2::CellInfo>& records);
+void convertRilCellInfoListToHal_1_4(void* response, size_t responseLen,
+ hidl_vec<V1_4::CellInfo>& records);
+void convertRilCellInfoListToHal_1_5(void* response, size_t responseLen,
+ hidl_vec<V1_5::CellInfo>& records);
+void convertRilCellInfoListToHal_1_6(void* response, size_t responseLen,
+ hidl_vec<V1_6::CellInfo>& records);
void populateResponseInfo(RadioResponseInfo& responseInfo, int serial, int responseType,
RIL_Errno e);
@@ -953,11 +984,11 @@
int counter = isRadioService ? mCounterRadio[slotId] : mCounterOemHook[slotId];
pthread_rwlock_t *radioServiceRwlockPtr = radio_1_6::getRadioServiceRwlock(slotId);
int ret = pthread_rwlock_unlock(radioServiceRwlockPtr);
- assert(ret == 0);
+ CHECK_EQ(ret, 0);
// acquire wrlock
ret = pthread_rwlock_wrlock(radioServiceRwlockPtr);
- assert(ret == 0);
+ CHECK_EQ(ret, 0);
// make sure the counter value has not changed
if (counter == (isRadioService ? mCounterRadio[slotId] : mCounterOemHook[slotId])) {
@@ -986,11 +1017,11 @@
// release wrlock
ret = pthread_rwlock_unlock(radioServiceRwlockPtr);
- assert(ret == 0);
+ CHECK_EQ(ret, 0);
// Reacquire rdlock
ret = pthread_rwlock_rdlock(radioServiceRwlockPtr);
- assert(ret == 0);
+ CHECK_EQ(ret, 0);
}
}
@@ -1005,7 +1036,7 @@
pthread_rwlock_t *radioServiceRwlockPtr = radio_1_6::getRadioServiceRwlock(mSlotId);
int ret = pthread_rwlock_wrlock(radioServiceRwlockPtr);
- assert(ret == 0);
+ CHECK_EQ(ret, 0);
mRadioResponse = radioResponseParam;
mRadioIndication = radioIndicationParam;
@@ -1055,7 +1086,7 @@
mCounterRadio[mSlotId]++;
ret = pthread_rwlock_unlock(radioServiceRwlockPtr);
- assert(ret == 0);
+ CHECK_EQ(ret, 0);
// client is connected. Send initial indications.
android::onNewCommandConnect((RIL_SOCKET_ID) mSlotId);
@@ -2428,7 +2459,7 @@
#if VDBG
RLOGD("getCellInfoList_1_6: serial %d", serial);
#endif
- dispatchVoid(serial, mSlotId, RIL_REQUEST_GET_CELL_INFO_LIST);
+ dispatchVoid(serial, mSlotId, RIL_REQUEST_GET_CELL_INFO_LIST_1_6);
return Void();
}
@@ -3501,12 +3532,14 @@
e = RIL_E_SUCCESS;
}
populateResponseInfo(responseInfo, serial, RESPONSE_SOLICITED, e);
- Return<void> retStatus
- = radioService[mSlotId]->mRadioResponseV1_2->setSignalStrengthReportingCriteriaResponse(responseInfo);
+ Return<void> retStatus =
+ radioService[mSlotId]
+ ->mRadioResponseV1_2->setSignalStrengthReportingCriteriaResponse(
+ responseInfo);
radioService[mSlotId]->checkReturnStatus(retStatus);
} else {
- RLOGE("setIndicationFilterResponse: radioService[%d]->mRadioResponse == NULL",
- mSlotId);
+ RLOGE("setSignalStrengthReportingCriteria: radioService[%d]->mRadioResponse == NULL",
+ mSlotId);
}
return Void();
}
@@ -4458,8 +4491,9 @@
populateResponseInfo(responseInfo, serial, RESPONSE_SOLICITED, RIL_E_SUCCESS);
if (radioService[mSlotId]->mRadioResponseV1_5 != NULL) {
- Return<void> retStatus
- = radioService[mSlotId]->mRadioResponseV1_5->setInitialAttachApnResponse(responseInfo);
+ Return<void> retStatus =
+ radioService[mSlotId]->mRadioResponseV1_5->setInitialAttachApnResponse_1_5(
+ responseInfo);
} else if (radioService[mSlotId]->mRadioResponseV1_4 != NULL) {
Return<void> retStatus
= radioService[mSlotId]->mRadioResponseV1_4->setInitialAttachApnResponse(responseInfo);
@@ -4487,8 +4521,8 @@
populateResponseInfo(responseInfo, serial, RESPONSE_SOLICITED, RIL_E_SUCCESS);
if (radioService[mSlotId]->mRadioResponseV1_5 != NULL) {
- Return<void> retStatus
- = radioService[mSlotId]->mRadioResponseV1_5->setDataProfileResponse(responseInfo);
+ Return<void> retStatus =
+ radioService[mSlotId]->mRadioResponseV1_5->setDataProfileResponse_1_5(responseInfo);
} else if (radioService[mSlotId]->mRadioResponseV1_4 != NULL) {
Return<void> retStatus
= radioService[mSlotId]->mRadioResponseV1_4->setDataProfileResponse(responseInfo);
@@ -4504,12 +4538,13 @@
return Void();
}
-Return<void> RadioImpl_1_6::setIndicationFilter_1_5(int32_t /* serial */,
- hidl_bitfield<::android::hardware::radio::V1_5::IndicationFilter> /* indicationFilter */) {
- // TODO implement
+Return<void> RadioImpl_1_6::setIndicationFilter_1_5(
+ int32_t serial,
+ hidl_bitfield<::android::hardware::radio::V1_5::IndicationFilter> indicationFilter) {
#if VDBG
- RLOGE("setIndicationFilter_1_5: Method is not implemented");
+ RLOGE("setIndicationFilter_1_5: serial %d");
#endif
+ dispatchInts(serial, mSlotId, RIL_REQUEST_SET_UNSOLICITED_RESPONSE_FILTER, 1, indicationFilter);
return Void();
}
@@ -4739,14 +4774,14 @@
pthread_rwlock_t *radioServiceRwlockPtr = radio_1_6::getRadioServiceRwlock(mSlotId);
int ret = pthread_rwlock_wrlock(radioServiceRwlockPtr);
- assert(ret == 0);
+ CHECK_EQ(ret, 0);
mOemHookResponse = oemHookResponseParam;
mOemHookIndication = oemHookIndicationParam;
mCounterOemHook[mSlotId]++;
ret = pthread_rwlock_unlock(radioServiceRwlockPtr);
- assert(ret == 0);
+ CHECK_EQ(ret, 0);
return Void();
}
@@ -5123,57 +5158,134 @@
return 0;
}
-int radio_1_6::getCurrentCallsResponse(int slotId,
- int responseType, int serial, RIL_Errno e,
- void *response, size_t responseLen) {
+int radio_1_6::getCurrentCallsResponse(int slotId, int responseType, int serial, RIL_Errno e,
+ void* response, size_t responseLen) {
#if VDBG
RLOGD("getCurrentCallsResponse: serial %d", serial);
#endif
- if (radioService[slotId]->mRadioResponse != NULL) {
+ if (radioService[slotId]->mRadioResponseV1_6 != NULL ||
+ radioService[slotId]->mRadioResponseV1_2 != NULL ||
+ radioService[slotId]->mRadioResponse != NULL) {
+ V1_6::RadioResponseInfo responseInfo16 = {};
RadioResponseInfo responseInfo = {};
- populateResponseInfo(responseInfo, serial, responseType, e);
-
- hidl_vec<Call> calls;
- if ((response == NULL && responseLen != 0)
- || (responseLen % sizeof(RIL_Call *)) != 0) {
- RLOGE("getCurrentCallsResponse: Invalid response");
- if (e == RIL_E_SUCCESS) responseInfo.error = RadioError::INVALID_RESPONSE;
+ if (radioService[slotId]->mRadioResponseV1_6 != NULL) {
+ populateResponseInfo_1_6(responseInfo16, serial, responseType, e);
} else {
- int num = responseLen / sizeof(RIL_Call *);
- calls.resize(num);
-
- for (int i = 0 ; i < num ; i++) {
- RIL_Call *p_cur = ((RIL_Call **) response)[i];
- /* each call info */
- calls[i].state = (CallState) p_cur->state;
- calls[i].index = p_cur->index;
- calls[i].toa = p_cur->toa;
- calls[i].isMpty = p_cur->isMpty;
- calls[i].isMT = p_cur->isMT;
- calls[i].als = p_cur->als;
- calls[i].isVoice = p_cur->isVoice;
- calls[i].isVoicePrivacy = p_cur->isVoicePrivacy;
- calls[i].number = convertCharPtrToHidlString(p_cur->number);
- calls[i].numberPresentation = (CallPresentation) p_cur->numberPresentation;
- calls[i].name = convertCharPtrToHidlString(p_cur->name);
- calls[i].namePresentation = (CallPresentation) p_cur->namePresentation;
- if (p_cur->uusInfo != NULL && p_cur->uusInfo->uusData != NULL) {
- RIL_UUS_Info *uusInfo = p_cur->uusInfo;
- calls[i].uusInfo.resize(1);
- calls[i].uusInfo[0].uusType = (UusType) uusInfo->uusType;
- calls[i].uusInfo[0].uusDcs = (UusDcs) uusInfo->uusDcs;
- // convert uusInfo->uusData to a null-terminated string
- char *nullTermStr = strndup(uusInfo->uusData, uusInfo->uusLength);
- calls[i].uusInfo[0].uusData = nullTermStr;
- free(nullTermStr);
- }
- }
+ populateResponseInfo(responseInfo, serial, responseType, e);
}
+ if ((response == NULL && responseLen != 0) || (responseLen % sizeof(RIL_Call*)) != 0) {
+ RLOGE("getCurrentCallsResponse: Invalid response");
+ if (e == RIL_E_SUCCESS) {
+ responseInfo16.error = V1_6::RadioError::INVALID_RESPONSE;
+ responseInfo.error = RadioError::INVALID_RESPONSE;
+ }
+ return 0;
+ } else {
+ Return<void> retStatus;
+ if (radioService[slotId]->mRadioResponseV1_6 != NULL) {
+ hidl_vec<V1_6::Call> calls;
+ int num = responseLen / sizeof(RIL_Call*);
+ calls.resize(num);
- Return<void> retStatus = radioService[slotId]->mRadioResponse->
- getCurrentCallsResponse(responseInfo, calls);
- radioService[slotId]->checkReturnStatus(retStatus);
+ for (int i = 0; i < num; i++) {
+ RIL_Call* p_cur = ((RIL_Call**)response)[i];
+ /* each call info */
+ calls[i].base.base.state = (CallState)p_cur->state;
+ calls[i].base.base.index = p_cur->index;
+ calls[i].base.base.toa = p_cur->toa;
+ calls[i].base.base.isMpty = p_cur->isMpty;
+ calls[i].base.base.isMT = p_cur->isMT;
+ calls[i].base.base.als = p_cur->als;
+ calls[i].base.base.isVoice = p_cur->isVoice;
+ calls[i].base.base.isVoicePrivacy = p_cur->isVoicePrivacy;
+ calls[i].base.base.number = convertCharPtrToHidlString(p_cur->number);
+ calls[i].base.base.numberPresentation =
+ (CallPresentation)p_cur->numberPresentation;
+ calls[i].base.base.name = convertCharPtrToHidlString(p_cur->name);
+ calls[i].base.base.namePresentation = (CallPresentation)p_cur->namePresentation;
+ if (p_cur->uusInfo != NULL && p_cur->uusInfo->uusData != NULL) {
+ RIL_UUS_Info* uusInfo = p_cur->uusInfo;
+ calls[i].base.base.uusInfo.resize(1);
+ calls[i].base.base.uusInfo[0].uusType = (UusType)uusInfo->uusType;
+ calls[i].base.base.uusInfo[0].uusDcs = (UusDcs)uusInfo->uusDcs;
+ // convert uusInfo->uusData to a null-terminated string
+ char* nullTermStr = strndup(uusInfo->uusData, uusInfo->uusLength);
+ calls[i].base.base.uusInfo[0].uusData = nullTermStr;
+ free(nullTermStr);
+ }
+ }
+ retStatus = radioService[slotId]->mRadioResponseV1_6->getCurrentCallsResponse_1_6(
+ responseInfo16, calls);
+ } else if (radioService[slotId]->mRadioResponseV1_2 != NULL) {
+ hidl_vec<V1_2::Call> calls;
+ int num = responseLen / sizeof(RIL_Call*);
+ calls.resize(num);
+
+ for (int i = 0; i < num; i++) {
+ RIL_Call* p_cur = ((RIL_Call**)response)[i];
+ /* each call info */
+ calls[i].base.state = (CallState)p_cur->state;
+ calls[i].base.index = p_cur->index;
+ calls[i].base.toa = p_cur->toa;
+ calls[i].base.isMpty = p_cur->isMpty;
+ calls[i].base.isMT = p_cur->isMT;
+ calls[i].base.als = p_cur->als;
+ calls[i].base.isVoice = p_cur->isVoice;
+ calls[i].base.isVoicePrivacy = p_cur->isVoicePrivacy;
+ calls[i].base.number = convertCharPtrToHidlString(p_cur->number);
+ calls[i].base.numberPresentation = (CallPresentation)p_cur->numberPresentation;
+ calls[i].base.name = convertCharPtrToHidlString(p_cur->name);
+ calls[i].base.namePresentation = (CallPresentation)p_cur->namePresentation;
+ if (p_cur->uusInfo != NULL && p_cur->uusInfo->uusData != NULL) {
+ RIL_UUS_Info* uusInfo = p_cur->uusInfo;
+ calls[i].base.uusInfo.resize(1);
+ calls[i].base.uusInfo[0].uusType = (UusType)uusInfo->uusType;
+ calls[i].base.uusInfo[0].uusDcs = (UusDcs)uusInfo->uusDcs;
+ // convert uusInfo->uusData to a null-terminated string
+ char* nullTermStr = strndup(uusInfo->uusData, uusInfo->uusLength);
+ calls[i].base.uusInfo[0].uusData = nullTermStr;
+ free(nullTermStr);
+ }
+ }
+ retStatus = radioService[slotId]->mRadioResponseV1_2->getCurrentCallsResponse_1_2(
+ responseInfo, calls);
+ } else {
+ hidl_vec<Call> calls;
+ int num = responseLen / sizeof(RIL_Call*);
+ calls.resize(num);
+
+ for (int i = 0; i < num; i++) {
+ RIL_Call* p_cur = ((RIL_Call**)response)[i];
+ /* each call info */
+ calls[i].state = (CallState)p_cur->state;
+ calls[i].index = p_cur->index;
+ calls[i].toa = p_cur->toa;
+ calls[i].isMpty = p_cur->isMpty;
+ calls[i].isMT = p_cur->isMT;
+ calls[i].als = p_cur->als;
+ calls[i].isVoice = p_cur->isVoice;
+ calls[i].isVoicePrivacy = p_cur->isVoicePrivacy;
+ calls[i].number = convertCharPtrToHidlString(p_cur->number);
+ calls[i].numberPresentation = (CallPresentation)p_cur->numberPresentation;
+ calls[i].name = convertCharPtrToHidlString(p_cur->name);
+ calls[i].namePresentation = (CallPresentation)p_cur->namePresentation;
+ if (p_cur->uusInfo != NULL && p_cur->uusInfo->uusData != NULL) {
+ RIL_UUS_Info* uusInfo = p_cur->uusInfo;
+ calls[i].uusInfo.resize(1);
+ calls[i].uusInfo[0].uusType = (UusType)uusInfo->uusType;
+ calls[i].uusInfo[0].uusDcs = (UusDcs)uusInfo->uusDcs;
+ // convert uusInfo->uusData to a null-terminated string
+ char* nullTermStr = strndup(uusInfo->uusData, uusInfo->uusLength);
+ calls[i].uusInfo[0].uusData = nullTermStr;
+ free(nullTermStr);
+ }
+ }
+ retStatus = radioService[slotId]->mRadioResponse->getCurrentCallsResponse(
+ responseInfo, calls);
+ }
+ radioService[slotId]->checkReturnStatus(retStatus);
+ }
} else {
RLOGE("getCurrentCallsResponse: radioService[%d]->mRadioResponse == NULL", slotId);
}
@@ -5268,19 +5380,19 @@
RIL_Errno e, void *response,
size_t responseLen) {
#if VDBG
- RLOGD("hangupWaitingOrBackgroundResponse: serial %d", serial);
+ RLOGD("hangupForegroundResumeBackgroundResponse: serial %d", serial);
#endif
if (radioService[slotId]->mRadioResponse != NULL) {
RadioResponseInfo responseInfo = {};
populateResponseInfo(responseInfo, serial, responseType, e);
Return<void> retStatus =
- radioService[slotId]->mRadioResponse->hangupWaitingOrBackgroundResponse(
- responseInfo);
+ radioService[slotId]->mRadioResponse->hangupForegroundResumeBackgroundResponse(
+ responseInfo);
radioService[slotId]->checkReturnStatus(retStatus);
} else {
- RLOGE("hangupWaitingOrBackgroundResponse: radioService[%d]->mRadioResponse == NULL",
- slotId);
+ RLOGE("hangupForegroundResumeBackgroundResponse: radioService[%d]->mRadioResponse == NULL",
+ slotId);
}
return 0;
@@ -5362,7 +5474,7 @@
LastCallFailCauseInfo info = {};
info.vendorCause = hidl_string();
if (response == NULL) {
- RLOGE("getCurrentCallsResponse Invalid response: NULL");
+ RLOGE("getLastCallFailCauseResponse Invalid response: NULL");
if (e == RIL_E_SUCCESS) responseInfo.error = RadioError::INVALID_RESPONSE;
} else if (responseLen == sizeof(int)) {
int *pInt = (int *) response;
@@ -5372,7 +5484,7 @@
info.causeCode = (LastCallFailCause) pFailCauseInfo->cause_code;
info.vendorCause = convertCharPtrToHidlString(pFailCauseInfo->vendor_cause);
} else {
- RLOGE("getCurrentCallsResponse Invalid response: NULL");
+ RLOGE("getLastCallFailCauseResponse Invalid response: NULL");
if (e == RIL_E_SUCCESS) responseInfo.error = RadioError::INVALID_RESPONSE;
}
@@ -5387,46 +5499,59 @@
return 0;
}
-int radio_1_6::getSignalStrengthResponse(int slotId,
- int responseType, int serial, RIL_Errno e,
- void *response, size_t responseLen) {
+int radio_1_6::getSignalStrengthResponse(int slotId, int responseType, int serial, RIL_Errno e,
+ void* response, size_t responseLen) {
#if VDBG
RLOGD("getSignalStrengthResponse: serial %d", serial);
#endif
- if (radioService[slotId]->mRadioResponseV1_4 != NULL) {
- RadioResponseInfo responseInfo = {};
- populateResponseInfo(responseInfo, serial, responseType, e);
- ::android::hardware::radio::V1_4::SignalStrength signalStrength_1_4 = {};
- if (response == NULL || responseLen != sizeof(RIL_SignalStrength_v12)) {
- RLOGE("getSignalStrengthResponse: Invalid response");
- if (e == RIL_E_SUCCESS) responseInfo.error = RadioError::INVALID_RESPONSE;
- } else {
- convertRilSignalStrengthToHal_1_4(response, responseLen, signalStrength_1_4);
- }
-
- //TODO: future implementation needs to fill tdScdma, wcdma and nr signal strength.
-
- Return<void> retStatus = radioService[slotId]->mRadioResponseV1_4->
- getSignalStrengthResponse_1_4(responseInfo, signalStrength_1_4);
- radioService[slotId]->checkReturnStatus(retStatus);
- } else if (radioService[slotId]->mRadioResponse != NULL) {
- RadioResponseInfo responseInfo = {};
- populateResponseInfo(responseInfo, serial, responseType, e);
- SignalStrength signalStrength = {};
- if (response == NULL || responseLen != sizeof(RIL_SignalStrength_v12)) {
- RLOGE("getSignalStrengthResponse: Invalid response");
- if (e == RIL_E_SUCCESS) responseInfo.error = RadioError::INVALID_RESPONSE;
- } else {
- convertRilSignalStrengthToHal(response, responseLen, signalStrength);
- }
-
- Return<void> retStatus = radioService[slotId]->mRadioResponse->getSignalStrengthResponse(
- responseInfo, signalStrength);
- radioService[slotId]->checkReturnStatus(retStatus);
+ V1_6::RadioResponseInfo responseInfo16 = {};
+ RadioResponseInfo responseInfo = {};
+ if (radioService[slotId]->mRadioResponseV1_6 != NULL) {
+ populateResponseInfo_1_6(responseInfo16, serial, responseType, e);
} else {
- RLOGE("getSignalStrengthResponse: radioService[%d]->mRadioResponse == NULL",
- slotId);
+ populateResponseInfo(responseInfo, serial, responseType, e);
+ }
+
+ if (response == NULL || responseLen != sizeof(RIL_SignalStrength_v12)) {
+ RLOGE("getSignalStrengthResponse: Invalid response");
+ if (e == RIL_E_SUCCESS) {
+ responseInfo16.error = V1_6::RadioError::INVALID_RESPONSE;
+ responseInfo.error = RadioError::INVALID_RESPONSE;
+ }
+ } else {
+ if (radioService[slotId]->mRadioResponseV1_6 != NULL) {
+ V1_6::SignalStrength signalStrength_1_6 = {};
+ convertRilSignalStrengthToHal_1_6(response, responseLen, signalStrength_1_6);
+ Return<void> retStatus =
+ radioService[slotId]->mRadioResponseV1_6->getSignalStrengthResponse_1_6(
+ responseInfo16, signalStrength_1_6);
+ radioService[slotId]->checkReturnStatus(retStatus);
+ } else if (radioService[slotId]->mRadioResponseV1_4 != NULL) {
+ V1_4::SignalStrength signalStrength_1_4 = {};
+ convertRilSignalStrengthToHal_1_4(response, responseLen, signalStrength_1_4);
+ // TODO: future implementation needs to fill tdScdma, wcdma and nr signal strength.
+ Return<void> retStatus =
+ radioService[slotId]->mRadioResponseV1_4->getSignalStrengthResponse_1_4(
+ responseInfo, signalStrength_1_4);
+ radioService[slotId]->checkReturnStatus(retStatus);
+ } else if (radioService[slotId]->mRadioResponseV1_2 != NULL) {
+ V1_2::SignalStrength signalStrength_1_2 = {};
+ convertRilSignalStrengthToHal_1_2(response, responseLen, signalStrength_1_2);
+ Return<void> retStatus =
+ radioService[slotId]->mRadioResponseV1_2->getSignalStrengthResponse_1_2(
+ responseInfo, signalStrength_1_2);
+ radioService[slotId]->checkReturnStatus(retStatus);
+ } else if (radioService[slotId]->mRadioResponse != NULL) {
+ SignalStrength signalStrength = {};
+ convertRilSignalStrengthToHal(response, responseLen, signalStrength);
+ Return<void> retStatus =
+ radioService[slotId]->mRadioResponse->getSignalStrengthResponse(responseInfo,
+ signalStrength);
+ radioService[slotId]->checkReturnStatus(retStatus);
+ } else {
+ RLOGE("getSignalStrengthResponse: radioService[%d]->mRadioResponse == NULL", slotId);
+ }
}
return 0;
@@ -6483,9 +6608,8 @@
return 0;
}
-int radio_1_6::getDataRegistrationStateResponse(int slotId,
- int responseType, int serial, RIL_Errno e,
- void *response, size_t responseLen) {
+int radio_1_6::getDataRegistrationStateResponse(int slotId, int responseType, int serial,
+ RIL_Errno e, void* response, size_t responseLen) {
#if VDBG
RLOGD("getDataRegistrationStateResponse: serial %d", serial);
#endif
@@ -6547,11 +6671,11 @@
}
Return<void> retStatus =
- radioService[slotId]
- ->mRadioResponseV1_6
- ->getVoiceRegistrationStateResponse_1_6(
- responseInfo_1_6, regResponse);
+ radioService[slotId]
+ ->mRadioResponseV1_6->getDataRegistrationStateResponse_1_6(
+ responseInfo_1_6, regResponse);
radioService[slotId]->checkReturnStatus(retStatus);
+ return 0;
}
} else if (s_vendorFunctions->version <= 14 &&
radioService[slotId]->mRadioResponseV1_5 != NULL) {
@@ -6587,11 +6711,11 @@
numStrings, resp);
Return<void> retStatus =
- radioService[slotId]
- ->mRadioResponseV1_5
- ->getDataRegistrationStateResponse_1_5(
- responseInfo, regResponse);
+ radioService[slotId]
+ ->mRadioResponseV1_5->getDataRegistrationStateResponse_1_5(
+ responseInfo, regResponse);
radioService[slotId]->checkReturnStatus(retStatus);
+ return 0;
}
} else if (s_vendorFunctions->version <= 14 &&
radioService[slotId]->mRadioResponseV1_2 != NULL) {
@@ -6611,6 +6735,7 @@
Return<void> retStatus = radioService[slotId]->mRadioResponseV1_2->
getDataRegistrationStateResponse_1_2(responseInfo, dataRegResponse);
radioService[slotId]->checkReturnStatus(retStatus);
+ return 0;
}
} else if (s_vendorFunctions->version <= 14) {
int numStrings = responseLen / sizeof(char *);
@@ -6865,7 +6990,7 @@
result.trafficDescriptors =
hidl_vec<::android::hardware::radio::V1_6::TrafficDescriptor>();
} else {
- convertRilDataCallToHal((RIL_Data_Call_Response_v12 *) response, result);
+ convertRilDataCallToHal((RIL_Data_Call_Response_v11*)response, result);
}
Return<void> retStatus = radioService[slotId]->mRadioResponseV1_6->setupDataCallResponse_1_6(
@@ -6888,7 +7013,7 @@
result.gateways = hidl_vec<hidl_string>();
result.pcscf = hidl_vec<hidl_string>();
} else {
- convertRilDataCallToHal((RIL_Data_Call_Response_v12 *) response, result);
+ convertRilDataCallToHal((RIL_Data_Call_Response_v11*)response, result);
}
Return<void> retStatus = radioService[slotId]->mRadioResponseV1_5->setupDataCallResponse_1_5(
@@ -7605,29 +7730,56 @@
return 0;
}
-int radio_1_6::getDataCallListResponse(int slotId,
- int responseType, int serial, RIL_Errno e,
- void *response, size_t responseLen) {
+int radio_1_6::getDataCallListResponse(int slotId, int responseType, int serial, RIL_Errno e,
+ void* response, size_t responseLen) {
#if VDBG
RLOGD("getDataCallListResponse: serial %d", serial);
#endif
- if (radioService[slotId]->mRadioResponse != NULL) {
+ if (radioService[slotId]->mRadioResponse != NULL ||
+ radioService[slotId]->mRadioResponseV1_4 != NULL ||
+ radioService[slotId]->mRadioResponseV1_5 != NULL ||
+ radioService[slotId]->mRadioResponseV1_6 != NULL) {
+ V1_6::RadioResponseInfo responseInfo16 = {};
RadioResponseInfo responseInfo = {};
- populateResponseInfo(responseInfo, serial, responseType, e);
+ if (radioService[slotId]->mRadioResponseV1_6 != NULL) {
+ populateResponseInfo_1_6(responseInfo16, serial, responseType, e);
+ } else {
+ populateResponseInfo(responseInfo, serial, responseType, e);
+ }
- hidl_vec<SetupDataCallResult> ret;
if ((response == NULL && responseLen != 0)
|| responseLen % sizeof(RIL_Data_Call_Response_v11) != 0) {
RLOGE("getDataCallListResponse: invalid response");
- if (e == RIL_E_SUCCESS) responseInfo.error = RadioError::INVALID_RESPONSE;
+ if (e == RIL_E_SUCCESS) {
+ responseInfo16.error = V1_6::RadioError::INVALID_RESPONSE;
+ responseInfo.error = RadioError::INVALID_RESPONSE;
+ }
} else {
- convertRilDataCallListToHal(response, responseLen, ret);
+ Return<void> retStatus;
+ if (radioService[slotId]->mRadioResponseV1_6 != NULL) {
+ hidl_vec<V1_6::SetupDataCallResult> ret;
+ convertRilDataCallListToHal_1_6(response, responseLen, ret);
+ retStatus = radioService[slotId]->mRadioResponseV1_6->getDataCallListResponse_1_6(
+ responseInfo16, ret);
+ } else if (radioService[slotId]->mRadioResponseV1_5 != NULL) {
+ hidl_vec<V1_5::SetupDataCallResult> ret;
+ convertRilDataCallListToHal_1_5(response, responseLen, ret);
+ retStatus = radioService[slotId]->mRadioResponseV1_5->getDataCallListResponse_1_5(
+ responseInfo, ret);
+ } else if (radioService[slotId]->mRadioResponseV1_4 != NULL) {
+ hidl_vec<V1_4::SetupDataCallResult> ret;
+ convertRilDataCallListToHal_1_4(response, responseLen, ret);
+ retStatus = radioService[slotId]->mRadioResponseV1_4->getDataCallListResponse_1_4(
+ responseInfo, ret);
+ } else {
+ hidl_vec<SetupDataCallResult> ret;
+ convertRilDataCallListToHal(response, responseLen, ret);
+ retStatus = radioService[slotId]->mRadioResponse->getDataCallListResponse(
+ responseInfo, ret);
+ }
+ radioService[slotId]->checkReturnStatus(retStatus);
}
-
- Return<void> retStatus = radioService[slotId]->mRadioResponse->getDataCallListResponse(
- responseInfo, ret);
- radioService[slotId]->checkReturnStatus(retStatus);
} else {
RLOGE("getDataCallListResponse: radioService[%d]->mRadioResponse == NULL", slotId);
}
@@ -8794,47 +8946,72 @@
return 0;
}
-int radio_1_6::getCellInfoListResponse(int slotId,
- int responseType,
- int serial, RIL_Errno e, void *response,
- size_t responseLen) {
+int radio_1_6::getCellInfoListResponse(int slotId, int responseType, int serial, RIL_Errno e,
+ void* response, size_t responseLen) {
#if VDBG
RLOGD("getCellInfoListResponse: serial %d", serial);
#endif
-
- if (radioService[slotId]->mRadioResponse != NULL ||
- radioService[slotId]->mRadioResponseV1_2 != NULL) {
+ if (radioService[slotId]->mRadioResponseV1_6 != NULL) {
+ V1_6::RadioResponseInfo responseInfo = {};
+ populateResponseInfo_1_6(responseInfo, serial, responseType, e);
+ hidl_vec<V1_6::CellInfo> ret;
+ Return<void> retStatus;
+ if (response != NULL && responseLen != 0 && responseLen % sizeof(RIL_CellInfo_v16) == 0) {
+ convertRilCellInfoListToHal_1_6(response, responseLen, ret);
+ } else {
+ RLOGE("getCellInfoListResponse_1_6: Invalid response");
+ if (e == RIL_E_SUCCESS) responseInfo.error = V1_6::RadioError::INVALID_RESPONSE;
+ }
+ retStatus = radioService[slotId]->mRadioResponseV1_6->getCellInfoListResponse_1_6(
+ responseInfo, ret);
+ radioService[slotId]->checkReturnStatus(retStatus);
+ } else if (radioService[slotId]->mRadioResponse != NULL ||
+ radioService[slotId]->mRadioResponseV1_2 != NULL ||
+ radioService[slotId]->mRadioResponseV1_4 != NULL ||
+ radioService[slotId]->mRadioResponseV1_5 != NULL) {
RadioResponseInfo responseInfo = {};
populateResponseInfo(responseInfo, serial, responseType, e);
-
+ bool error = response == NULL && responseLen != 0;
Return<void> retStatus;
- hidl_vec<CellInfo> ret;
- if ((response == NULL && responseLen != 0)
- || responseLen % sizeof(RIL_CellInfo_v12) != 0) {
- RLOGE("getCellInfoListResponse: Invalid response");
- if (e == RIL_E_SUCCESS) responseInfo.error = RadioError::INVALID_RESPONSE;
-
- if (radioService[slotId]->mRadioResponseV1_2 != NULL) {
- hidl_vec<V1_2::CellInfo> ret;
- retStatus = radioService[slotId]->mRadioResponseV1_2->
- getCellInfoListResponse_1_2(responseInfo, ret);
+ if (radioService[slotId]->mRadioResponseV1_5 != NULL) {
+ hidl_vec<V1_5::CellInfo> ret;
+ if (!error && responseLen % sizeof(RIL_CellInfo_v16) != 0) {
+ convertRilCellInfoListToHal_1_5(response, responseLen, ret);
} else {
- hidl_vec<CellInfo> ret;
- retStatus = radioService[slotId]->mRadioResponse->
- getCellInfoListResponse(responseInfo, ret);
+ RLOGE("getCellInfoListResponse_1_5: Invalid response");
+ if (e == RIL_E_SUCCESS) responseInfo.error = RadioError::INVALID_RESPONSE;
}
- } else {
- if (radioService[slotId]->mRadioResponseV1_2 != NULL) {
- hidl_vec<V1_2::CellInfo> ret;
+ retStatus = radioService[slotId]->mRadioResponseV1_5->getCellInfoListResponse_1_5(
+ responseInfo, ret);
+ } else if (radioService[slotId]->mRadioResponseV1_4 != NULL) {
+ hidl_vec<V1_4::CellInfo> ret;
+ if (!error && responseLen % sizeof(RIL_CellInfo_v16) != 0) {
+ convertRilCellInfoListToHal_1_4(response, responseLen, ret);
+ } else {
+ RLOGE("getCellInfoListResponse_1_4: Invalid response");
+ if (e == RIL_E_SUCCESS) responseInfo.error = RadioError::INVALID_RESPONSE;
+ }
+ radioService[slotId]->mRadioResponseV1_4->getCellInfoListResponse_1_4(responseInfo,
+ ret);
+ } else if (radioService[slotId]->mRadioResponseV1_2 != NULL) {
+ hidl_vec<V1_2::CellInfo> ret;
+ if (!error && responseLen % sizeof(RIL_CellInfo_v12) != 0) {
convertRilCellInfoListToHal_1_2(response, responseLen, ret);
- retStatus = radioService[slotId]->mRadioResponseV1_2->
- getCellInfoListResponse_1_2(responseInfo, ret);
} else {
- hidl_vec<CellInfo> ret;
- convertRilCellInfoListToHal(response, responseLen, ret);
- retStatus = radioService[slotId]->mRadioResponse->
- getCellInfoListResponse(responseInfo, ret);
+ RLOGE("getCellInfoListResponse_1_2: Invalid response");
+ if (e == RIL_E_SUCCESS) responseInfo.error = RadioError::INVALID_RESPONSE;
}
+ radioService[slotId]->mRadioResponseV1_2->getCellInfoListResponse_1_2(responseInfo,
+ ret);
+ } else {
+ hidl_vec<CellInfo> ret;
+ if (!error && responseLen % sizeof(RIL_CellInfo) != 0) {
+ convertRilCellInfoListToHal(response, responseLen, ret);
+ } else {
+ RLOGE("getCellInfoListResponse: Invalid response");
+ if (e == RIL_E_SUCCESS) responseInfo.error = RadioError::INVALID_RESPONSE;
+ }
+ radioService[slotId]->mRadioResponse->getCellInfoListResponse(responseInfo, ret);
}
radioService[slotId]->checkReturnStatus(retStatus);
} else {
@@ -8866,9 +9043,8 @@
return 0;
}
-int radio_1_6::setInitialAttachApnResponse(int slotId,
- int responseType, int serial, RIL_Errno e,
- void *response, size_t responseLen) {
+int radio_1_6::setInitialAttachApnResponse(int slotId, int responseType, int serial, RIL_Errno e,
+ void* response, size_t responseLen) {
#if VDBG
RLOGD("setInitialAttachApnResponse: serial %d", serial);
#endif
@@ -8876,18 +9052,18 @@
if (radioService[slotId]->mRadioResponseV1_5 != NULL) {
RadioResponseInfo responseInfo = {};
populateResponseInfo(responseInfo, serial, responseType, e);
- Return<void> retStatus
- = radioService[slotId]->mRadioResponseV1_5->setInitialAttachApnResponse_1_5(
- responseInfo);
+ Return<void> retStatus =
+ radioService[slotId]->mRadioResponseV1_5->setInitialAttachApnResponse_1_5(
+ responseInfo);
+ radioService[slotId]->checkReturnStatus(retStatus);
} else if (radioService[slotId]->mRadioResponse != NULL) {
RadioResponseInfo responseInfo = {};
populateResponseInfo(responseInfo, serial, responseType, e);
- Return<void> retStatus
- = radioService[slotId]->mRadioResponse->setInitialAttachApnResponse(responseInfo);
+ Return<void> retStatus =
+ radioService[slotId]->mRadioResponse->setInitialAttachApnResponse(responseInfo);
radioService[slotId]->checkReturnStatus(retStatus);
} else {
- RLOGE("setInitialAttachApnResponse: radioService[%d]->mRadioResponse == NULL",
- slotId);
+ RLOGE("setInitialAttachApnResponse: radioService[%d]->mRadioResponse == NULL", slotId);
}
return 0;
@@ -9469,6 +9645,7 @@
RLOGD("setAllowedCarriersResponse: serial %d", serial);
#endif
RadioResponseInfo responseInfo = {};
+ populateResponseInfo(responseInfo, serial, responseType, e);
if (radioService[slotId]->mRadioResponseV1_4 != NULL) {
Return<void> retStatus = radioService[slotId]->mRadioResponseV1_4
@@ -10322,7 +10499,7 @@
RLOGD("getSlicingConfigResponse: serial %d", serial);
#endif
- if (radioService[slotId]->mRadioResponse != NULL) {
+ if (radioService[slotId]->mRadioResponseV1_6 != NULL) {
V1_6::RadioResponseInfo responseInfo = {};
populateResponseInfo_1_6(responseInfo, serial, responseType, e);
@@ -10342,22 +10519,64 @@
#if VDBG
RLOGD("getSimPhonebookRecordsResponse: serial %d", serial);
#endif
+
+ if (radioService[slotId]->mRadioResponseV1_6 != NULL) {
+ V1_6::RadioResponseInfo responseInfo = {};
+ populateResponseInfo_1_6(responseInfo, serial, responseType, e);
+
+ Return<void> retStatus =
+ radioService[slotId]->mRadioResponseV1_6->getSimPhonebookRecordsResponse(
+ responseInfo);
+ radioService[slotId]->checkReturnStatus(retStatus);
+ } else {
+ RLOGE("getSimPhonebookRecordsResponse: radioService[%d]->mRadioResponse == NULL", slotId);
+ }
+
return 0;
}
int radio_1_6::getSimPhonebookCapacityResponse(int slotId, int responseType, int serial,
RIL_Errno e, void *response, size_t responseLen) {
#if VDBG
- RLOGD("getSimPhonebookRecordsResponse: serial %d", serial);
+ RLOGD("getSimPhonebookCapacityResponse: serial %d", serial);
#endif
+
+ if (radioService[slotId]->mRadioResponseV1_6 != NULL) {
+ V1_6::RadioResponseInfo responseInfo = {};
+ populateResponseInfo_1_6(responseInfo, serial, responseType, e);
+
+ V1_6::PhonebookCapacity phonebookCapacity = {};
+ Return<void> retStatus =
+ radioService[slotId]->mRadioResponseV1_6->getSimPhonebookCapacityResponse(
+ responseInfo, phonebookCapacity);
+ radioService[slotId]->checkReturnStatus(retStatus);
+ } else {
+ RLOGE("getSimPhonebookCapacityResponse: radioService[%d]->mRadioResponse == NULL", slotId);
+ }
+
return 0;
}
int radio_1_6::updateSimPhonebookRecordsResponse(int slotId, int responseType, int serial,
RIL_Errno e, void *response, size_t responseLen) {
#if VDBG
- RLOGD("getSimPhonebookRecordsResponse: serial %d", serial);
+ RLOGD("updateSimPhonebookRecordsResponse: serial %d", serial);
#endif
+
+ if (radioService[slotId]->mRadioResponseV1_6 != NULL) {
+ V1_6::RadioResponseInfo responseInfo = {};
+ populateResponseInfo_1_6(responseInfo, serial, responseType, e);
+
+ int32_t updatedRecordIndex = 0;
+ Return<void> retStatus =
+ radioService[slotId]->mRadioResponseV1_6->updateSimPhonebookRecordsResponse(
+ responseInfo, updatedRecordIndex);
+ radioService[slotId]->checkReturnStatus(retStatus);
+ } else {
+ RLOGE("updateSimPhonebookRecordsResponse: radioService[%d]->mRadioResponse == NULL",
+ slotId);
+ }
+
return 0;
}
@@ -10638,6 +10857,26 @@
signalStrength.tdScdma.rscp = rilSignalStrength->TD_SCDMA_SignalStrength.rscp;
}
+void convertRilSignalStrengthToHal_1_2(void* response, size_t responseLen,
+ V1_2::SignalStrength& signalStrength_1_2) {
+ SignalStrength signalStrength = {};
+ convertRilSignalStrengthToHal(response, responseLen, signalStrength);
+ signalStrength_1_2.gsm = signalStrength.gw;
+ signalStrength_1_2.cdma = signalStrength.cdma;
+ signalStrength_1_2.evdo = signalStrength.evdo;
+ signalStrength_1_2.lte = signalStrength.lte;
+
+ RIL_SignalStrength_v12* rilSignalStrength = (RIL_SignalStrength_v12*)response;
+ signalStrength_1_2.wcdma.base.signalStrength =
+ rilSignalStrength->WCDMA_SignalStrength.signalStrength;
+ signalStrength_1_2.wcdma.base.bitErrorRate =
+ rilSignalStrength->WCDMA_SignalStrength.bitErrorRate;
+ signalStrength_1_2.wcdma.rscp = INT_MAX;
+ signalStrength_1_2.wcdma.ecno = INT_MAX;
+
+ signalStrength_1_2.tdScdma.rscp = INT_MAX;
+}
+
void convertRilSignalStrengthToHal_1_4(void *response, size_t responseLen,
V1_4::SignalStrength& signalStrength_1_4) {
SignalStrength signalStrength = {};
@@ -10667,12 +10906,41 @@
signalStrength_1_4.nr.csiSinr = rilSignalStrength->NR_SignalStrength.ssSinr;
}
-int radio_1_6::currentSignalStrengthInd(int slotId,
- int indicationType, int token, RIL_Errno e,
- void *response, size_t responseLen) {
- if (radioService[slotId] != NULL &&
- (radioService[slotId]->mRadioIndication != NULL ||
- radioService[slotId]->mRadioIndicationV1_4 != NULL)) {
+void convertRilSignalStrengthToHal_1_6(void* response, size_t responseLen,
+ V1_6::SignalStrength& signalStrength_1_6) {
+ SignalStrength signalStrength = {};
+ convertRilSignalStrengthToHal(response, responseLen, signalStrength);
+ signalStrength_1_6.gsm = signalStrength.gw;
+ signalStrength_1_6.cdma = signalStrength.cdma;
+ signalStrength_1_6.evdo = signalStrength.evdo;
+ signalStrength_1_6.lte.base = signalStrength.lte;
+
+ RIL_SignalStrength_v12* rilSignalStrength = (RIL_SignalStrength_v12*)response;
+ signalStrength_1_6.wcdma.base.signalStrength =
+ rilSignalStrength->WCDMA_SignalStrength.signalStrength;
+ signalStrength_1_6.wcdma.base.bitErrorRate =
+ rilSignalStrength->WCDMA_SignalStrength.bitErrorRate;
+ signalStrength_1_6.wcdma.rscp = INT_MAX;
+ signalStrength_1_6.wcdma.ecno = INT_MAX;
+
+ signalStrength_1_6.tdscdma.signalStrength = INT_MAX;
+ signalStrength_1_6.tdscdma.bitErrorRate = INT_MAX;
+ signalStrength_1_6.tdscdma.rscp = INT_MAX;
+
+ signalStrength_1_6.nr.base.ssRsrp = rilSignalStrength->NR_SignalStrength.ssRsrp;
+ signalStrength_1_6.nr.base.ssRsrq = rilSignalStrength->NR_SignalStrength.ssRsrq;
+ signalStrength_1_6.nr.base.ssSinr = rilSignalStrength->NR_SignalStrength.ssSinr;
+ signalStrength_1_6.nr.base.csiRsrp = rilSignalStrength->NR_SignalStrength.csiRsrp;
+ signalStrength_1_6.nr.base.csiRsrq = rilSignalStrength->NR_SignalStrength.csiRsrq;
+ signalStrength_1_6.nr.base.csiSinr = rilSignalStrength->NR_SignalStrength.ssSinr;
+}
+
+int radio_1_6::currentSignalStrengthInd(int slotId, int indicationType, int token, RIL_Errno e,
+ void* response, size_t responseLen) {
+ if (radioService[slotId] != NULL && (radioService[slotId]->mRadioIndication != NULL ||
+ radioService[slotId]->mRadioIndicationV1_2 != NULL ||
+ radioService[slotId]->mRadioIndicationV1_4 != NULL ||
+ radioService[slotId]->mRadioIndicationV1_6 != NULL)) {
if (response == NULL || responseLen != sizeof(RIL_SignalStrength_v12)) {
RLOGE("currentSignalStrengthInd: invalid response");
return 0;
@@ -10682,16 +10950,26 @@
RLOGD("currentSignalStrengthInd");
#endif
Return<void> retStatus;
- if (radioService[slotId]->mRadioIndicationV1_4 != NULL) {
- V1_4::SignalStrength signalStrength_1_4 = {};
- convertRilSignalStrengthToHal_1_4(response, responseLen, signalStrength_1_4);
- retStatus = radioService[slotId]->mRadioIndicationV1_4->currentSignalStrength_1_4(
- convertIntToRadioIndicationType(indicationType), signalStrength_1_4);
+ if (radioService[slotId]->mRadioIndicationV1_6 != NULL) {
+ V1_6::SignalStrength signalStrength_1_6 = {};
+ convertRilSignalStrengthToHal_1_6(response, responseLen, signalStrength_1_6);
+ retStatus = radioService[slotId]->mRadioIndicationV1_6->currentSignalStrength_1_6(
+ convertIntToRadioIndicationType(indicationType), signalStrength_1_6);
+ } else if (radioService[slotId]->mRadioIndicationV1_4 != NULL) {
+ V1_4::SignalStrength signalStrength_1_4 = {};
+ convertRilSignalStrengthToHal_1_4(response, responseLen, signalStrength_1_4);
+ retStatus = radioService[slotId]->mRadioIndicationV1_4->currentSignalStrength_1_4(
+ convertIntToRadioIndicationType(indicationType), signalStrength_1_4);
+ } else if (radioService[slotId]->mRadioIndicationV1_2 != NULL) {
+ V1_2::SignalStrength signalStrength_1_2 = {};
+ convertRilSignalStrengthToHal_1_2(response, responseLen, signalStrength_1_2);
+ retStatus = radioService[slotId]->mRadioIndicationV1_2->currentSignalStrength_1_2(
+ convertIntToRadioIndicationType(indicationType), signalStrength_1_2);
} else {
- SignalStrength signalStrength = {};
- convertRilSignalStrengthToHal(response, responseLen, signalStrength);
- retStatus = radioService[slotId]->mRadioIndication->currentSignalStrength(
- convertIntToRadioIndicationType(indicationType), signalStrength);
+ SignalStrength signalStrength = {};
+ convertRilSignalStrengthToHal(response, responseLen, signalStrength);
+ retStatus = radioService[slotId]->mRadioIndication->currentSignalStrength(
+ convertIntToRadioIndicationType(indicationType), signalStrength);
}
radioService[slotId]->checkReturnStatus(retStatus);
} else {
@@ -10763,8 +11041,8 @@
dcResult.mtu = dcResponse->mtu;
}
-void convertRilDataCallToHal(RIL_Data_Call_Response_v12 *dcResponse,
- ::android::hardware::radio::V1_5::SetupDataCallResult& dcResult) {
+void convertRilDataCallToHal(RIL_Data_Call_Response_v11* dcResponse,
+ ::android::hardware::radio::V1_5::SetupDataCallResult& dcResult) {
dcResult.cause = (::android::hardware::radio::V1_4::DataCallFailCause) dcResponse->status;
dcResult.suggestedRetryTime = dcResponse->suggestedRetryTime;
dcResult.cid = dcResponse->cid;
@@ -10788,12 +11066,12 @@
dcResult.dnses = split(convertCharPtrToHidlString(dcResponse->dnses));
dcResult.gateways = split(convertCharPtrToHidlString(dcResponse->gateways));
dcResult.pcscf = split(convertCharPtrToHidlString(dcResponse->pcscf));
- dcResult.mtuV4 = dcResponse->mtuV4;
- dcResult.mtuV6 = dcResponse->mtuV6;
+ dcResult.mtuV4 = dcResponse->mtu;
+ dcResult.mtuV6 = dcResponse->mtu;
}
-void convertRilDataCallToHal(RIL_Data_Call_Response_v12 *dcResponse,
- ::android::hardware::radio::V1_6::SetupDataCallResult& dcResult) {
+void convertRilDataCallToHal(RIL_Data_Call_Response_v11* dcResponse,
+ ::android::hardware::radio::V1_6::SetupDataCallResult& dcResult) {
dcResult.cause = (::android::hardware::radio::V1_6::DataCallFailCause) dcResponse->status;
dcResult.suggestedRetryTime = dcResponse->suggestedRetryTime;
dcResult.cid = dcResponse->cid;
@@ -10817,14 +11095,23 @@
dcResult.dnses = split(convertCharPtrToHidlString(dcResponse->dnses));
dcResult.gateways = split(convertCharPtrToHidlString(dcResponse->gateways));
dcResult.pcscf = split(convertCharPtrToHidlString(dcResponse->pcscf));
- dcResult.mtuV4 = dcResponse->mtuV4;
- dcResult.mtuV6 = dcResponse->mtuV6;
+ dcResult.mtuV4 = dcResponse->mtu;
+ dcResult.mtuV6 = dcResponse->mtu;
std::vector<::android::hardware::radio::V1_6::TrafficDescriptor> trafficDescriptors;
::android::hardware::radio::V1_6::TrafficDescriptor trafficDescriptor;
::android::hardware::radio::V1_6::OsAppId osAppId;
- osAppId.osAppId = 1;
+ std::vector<uint8_t> osAppIdVec;
+ osAppIdVec.push_back('o');
+ osAppIdVec.push_back('s');
+ osAppIdVec.push_back('A');
+ osAppIdVec.push_back('p');
+ osAppIdVec.push_back('p');
+ osAppIdVec.push_back('I');
+ osAppIdVec.push_back('d');
+
+ osAppId.osAppId = osAppIdVec;
trafficDescriptor.osAppId.value(osAppId);
trafficDescriptors.push_back(trafficDescriptor);
dcResult.trafficDescriptors = trafficDescriptors;
@@ -10841,22 +11128,76 @@
}
}
+void convertRilDataCallListToHal_1_4(void* response, size_t responseLen,
+ hidl_vec<V1_4::SetupDataCallResult>& dcResultList) {
+ int num = responseLen / sizeof(RIL_Data_Call_Response_v11);
+
+ RIL_Data_Call_Response_v11* dcResponse = (RIL_Data_Call_Response_v11*)response;
+ dcResultList.resize(num);
+ for (int i = 0; i < num; i++) {
+ convertRilDataCallToHal(&dcResponse[i], dcResultList[i]);
+ }
+}
+
+void convertRilDataCallListToHal_1_5(void* response, size_t responseLen,
+ hidl_vec<V1_5::SetupDataCallResult>& dcResultList) {
+ int num = responseLen / sizeof(RIL_Data_Call_Response_v11);
+
+ RIL_Data_Call_Response_v11* dcResponse = (RIL_Data_Call_Response_v11*)response;
+ dcResultList.resize(num);
+ for (int i = 0; i < num; i++) {
+ convertRilDataCallToHal(&dcResponse[i], dcResultList[i]);
+ }
+}
+
+void convertRilDataCallListToHal_1_6(void* response, size_t responseLen,
+ hidl_vec<V1_6::SetupDataCallResult>& dcResultList) {
+ int num = responseLen / sizeof(RIL_Data_Call_Response_v11);
+
+ RIL_Data_Call_Response_v11* dcResponse = (RIL_Data_Call_Response_v11*)response;
+ dcResultList.resize(num);
+ for (int i = 0; i < num; i++) {
+ convertRilDataCallToHal(&dcResponse[i], dcResultList[i]);
+ }
+}
+
int radio_1_6::dataCallListChangedInd(int slotId,
int indicationType, int token, RIL_Errno e, void *response,
size_t responseLen) {
- if (radioService[slotId] != NULL && radioService[slotId]->mRadioIndication != NULL) {
+ if (radioService[slotId] != NULL && (radioService[slotId]->mRadioIndication != NULL ||
+ radioService[slotId]->mRadioIndicationV1_4 != NULL ||
+ radioService[slotId]->mRadioIndicationV1_5 != NULL ||
+ radioService[slotId]->mRadioIndicationV1_6 != NULL)) {
if ((response == NULL && responseLen != 0)
|| responseLen % sizeof(RIL_Data_Call_Response_v11) != 0) {
RLOGE("dataCallListChangedInd: invalid response");
return 0;
}
- hidl_vec<SetupDataCallResult> dcList;
- convertRilDataCallListToHal(response, responseLen, dcList);
#if VDBG
RLOGD("dataCallListChangedInd");
#endif
- Return<void> retStatus = radioService[slotId]->mRadioIndication->dataCallListChanged(
- convertIntToRadioIndicationType(indicationType), dcList);
+ Return<void> retStatus;
+ if (radioService[slotId]->mRadioIndicationV1_6 != NULL) {
+ hidl_vec<V1_6::SetupDataCallResult> dcList;
+ convertRilDataCallListToHal_1_6(response, responseLen, dcList);
+ retStatus = radioService[slotId]->mRadioIndicationV1_6->dataCallListChanged_1_6(
+ convertIntToRadioIndicationType(indicationType), dcList);
+ } else if (radioService[slotId]->mRadioIndicationV1_5 != NULL) {
+ hidl_vec<V1_5::SetupDataCallResult> dcList;
+ convertRilDataCallListToHal_1_5(response, responseLen, dcList);
+ retStatus = radioService[slotId]->mRadioIndicationV1_5->dataCallListChanged_1_5(
+ convertIntToRadioIndicationType(indicationType), dcList);
+ } else if (radioService[slotId]->mRadioIndicationV1_4 != NULL) {
+ hidl_vec<V1_4::SetupDataCallResult> dcList;
+ convertRilDataCallListToHal_1_4(response, responseLen, dcList);
+ retStatus = radioService[slotId]->mRadioIndicationV1_4->dataCallListChanged_1_4(
+ convertIntToRadioIndicationType(indicationType), dcList);
+ } else {
+ hidl_vec<SetupDataCallResult> dcList;
+ convertRilDataCallListToHal(response, responseLen, dcList);
+ retStatus = radioService[slotId]->mRadioIndication->dataCallListChanged(
+ convertIntToRadioIndicationType(indicationType), dcList);
+ }
radioService[slotId]->checkReturnStatus(retStatus);
} else {
RLOGE("dataCallListChangedInd: radioService[%d]->mRadioIndication == NULL", slotId);
@@ -12045,6 +12386,351 @@
}
}
+void convertRilCellInfoListToHal_1_5(void* response, size_t responseLen,
+ hidl_vec<V1_5::CellInfo>& records) {
+ int num = responseLen / sizeof(RIL_CellInfo_v16);
+ records.resize(num);
+ RIL_CellInfo_v16* rillCellInfo = (RIL_CellInfo_v16*)response;
+ for (int i = 0; i < num; i++) {
+ records[i].registered = rillCellInfo->registered;
+ records[i].connectionStatus = (V1_2::CellConnectionStatus)rillCellInfo->connectionStatus;
+
+ switch (rillCellInfo->cellInfoType) {
+ case RIL_CELL_INFO_TYPE_GSM: {
+ V1_5::CellInfoGsm cellInfoGsm;
+ cellInfoGsm.cellIdentityGsm.base.base.mcc =
+ std::to_string(rillCellInfo->CellInfo.gsm.cellIdentityGsm.mcc);
+ cellInfoGsm.cellIdentityGsm.base.base.mnc =
+ ril::util::mnc::decode(rillCellInfo->CellInfo.gsm.cellIdentityGsm.mnc);
+ cellInfoGsm.cellIdentityGsm.base.base.lac =
+ rillCellInfo->CellInfo.gsm.cellIdentityGsm.lac;
+ cellInfoGsm.cellIdentityGsm.base.base.cid =
+ rillCellInfo->CellInfo.gsm.cellIdentityGsm.cid;
+ cellInfoGsm.cellIdentityGsm.base.base.arfcn =
+ rillCellInfo->CellInfo.gsm.cellIdentityGsm.arfcn;
+ cellInfoGsm.cellIdentityGsm.base.base.bsic =
+ rillCellInfo->CellInfo.gsm.cellIdentityGsm.bsic;
+ cellInfoGsm.signalStrengthGsm.signalStrength =
+ rillCellInfo->CellInfo.gsm.signalStrengthGsm.signalStrength;
+ cellInfoGsm.signalStrengthGsm.bitErrorRate =
+ rillCellInfo->CellInfo.gsm.signalStrengthGsm.bitErrorRate;
+ cellInfoGsm.signalStrengthGsm.timingAdvance =
+ rillCellInfo->CellInfo.gsm.signalStrengthGsm.timingAdvance;
+ records[i].ratSpecificInfo.gsm(cellInfoGsm);
+ break;
+ }
+
+ case RIL_CELL_INFO_TYPE_WCDMA: {
+ V1_5::CellInfoWcdma cellInfoWcdma;
+ cellInfoWcdma.cellIdentityWcdma.base.base.mcc =
+ std::to_string(rillCellInfo->CellInfo.wcdma.cellIdentityWcdma.mcc);
+ cellInfoWcdma.cellIdentityWcdma.base.base.mnc =
+ ril::util::mnc::decode(rillCellInfo->CellInfo.wcdma.cellIdentityWcdma.mnc);
+ cellInfoWcdma.cellIdentityWcdma.base.base.lac =
+ rillCellInfo->CellInfo.wcdma.cellIdentityWcdma.lac;
+ cellInfoWcdma.cellIdentityWcdma.base.base.cid =
+ rillCellInfo->CellInfo.wcdma.cellIdentityWcdma.cid;
+ cellInfoWcdma.cellIdentityWcdma.base.base.psc =
+ rillCellInfo->CellInfo.wcdma.cellIdentityWcdma.psc;
+ cellInfoWcdma.cellIdentityWcdma.base.base.uarfcn =
+ rillCellInfo->CellInfo.wcdma.cellIdentityWcdma.uarfcn;
+ cellInfoWcdma.signalStrengthWcdma.base.signalStrength =
+ rillCellInfo->CellInfo.wcdma.signalStrengthWcdma.signalStrength;
+ cellInfoWcdma.signalStrengthWcdma.base.bitErrorRate =
+ rillCellInfo->CellInfo.wcdma.signalStrengthWcdma.bitErrorRate;
+ records[i].ratSpecificInfo.wcdma(cellInfoWcdma);
+ break;
+ }
+
+ case RIL_CELL_INFO_TYPE_CDMA: {
+ V1_2::CellInfoCdma cellInfoCdma;
+ cellInfoCdma.cellIdentityCdma.base.networkId =
+ rillCellInfo->CellInfo.cdma.cellIdentityCdma.networkId;
+ cellInfoCdma.cellIdentityCdma.base.systemId =
+ rillCellInfo->CellInfo.cdma.cellIdentityCdma.systemId;
+ cellInfoCdma.cellIdentityCdma.base.baseStationId =
+ rillCellInfo->CellInfo.cdma.cellIdentityCdma.basestationId;
+ cellInfoCdma.cellIdentityCdma.base.longitude =
+ rillCellInfo->CellInfo.cdma.cellIdentityCdma.longitude;
+ cellInfoCdma.cellIdentityCdma.base.latitude =
+ rillCellInfo->CellInfo.cdma.cellIdentityCdma.latitude;
+ cellInfoCdma.signalStrengthCdma.dbm =
+ rillCellInfo->CellInfo.cdma.signalStrengthCdma.dbm;
+ cellInfoCdma.signalStrengthCdma.ecio =
+ rillCellInfo->CellInfo.cdma.signalStrengthCdma.ecio;
+ cellInfoCdma.signalStrengthEvdo.dbm =
+ rillCellInfo->CellInfo.cdma.signalStrengthEvdo.dbm;
+ cellInfoCdma.signalStrengthEvdo.ecio =
+ rillCellInfo->CellInfo.cdma.signalStrengthEvdo.ecio;
+ cellInfoCdma.signalStrengthEvdo.signalNoiseRatio =
+ rillCellInfo->CellInfo.cdma.signalStrengthEvdo.signalNoiseRatio;
+ records[i].ratSpecificInfo.cdma(cellInfoCdma);
+ break;
+ }
+
+ case RIL_CELL_INFO_TYPE_LTE: {
+ V1_5::CellInfoLte cellInfoLte;
+ cellInfoLte.cellIdentityLte.base.base.mcc =
+ std::to_string(rillCellInfo->CellInfo.lte.cellIdentityLte.mcc);
+ cellInfoLte.cellIdentityLte.base.base.mnc =
+ ril::util::mnc::decode(rillCellInfo->CellInfo.lte.cellIdentityLte.mnc);
+ cellInfoLte.cellIdentityLte.base.base.ci =
+ rillCellInfo->CellInfo.lte.cellIdentityLte.ci;
+ cellInfoLte.cellIdentityLte.base.base.pci =
+ rillCellInfo->CellInfo.lte.cellIdentityLte.pci;
+ cellInfoLte.cellIdentityLte.base.base.tac =
+ rillCellInfo->CellInfo.lte.cellIdentityLte.tac;
+ cellInfoLte.cellIdentityLte.base.base.earfcn =
+ rillCellInfo->CellInfo.lte.cellIdentityLte.earfcn;
+ cellInfoLte.signalStrengthLte.signalStrength =
+ rillCellInfo->CellInfo.lte.signalStrengthLte.signalStrength;
+ cellInfoLte.signalStrengthLte.rsrp =
+ rillCellInfo->CellInfo.lte.signalStrengthLte.rsrp;
+ cellInfoLte.signalStrengthLte.rsrq =
+ rillCellInfo->CellInfo.lte.signalStrengthLte.rsrq;
+ cellInfoLte.signalStrengthLte.rssnr =
+ rillCellInfo->CellInfo.lte.signalStrengthLte.rssnr;
+ cellInfoLte.signalStrengthLte.cqi =
+ rillCellInfo->CellInfo.lte.signalStrengthLte.cqi;
+ cellInfoLte.signalStrengthLte.timingAdvance =
+ rillCellInfo->CellInfo.lte.signalStrengthLte.timingAdvance;
+ records[i].ratSpecificInfo.lte(cellInfoLte);
+ break;
+ }
+
+ case RIL_CELL_INFO_TYPE_TD_SCDMA: {
+ V1_5::CellInfoTdscdma cellInfoTdscdma;
+ cellInfoTdscdma.cellIdentityTdscdma.base.base.mcc =
+ std::to_string(rillCellInfo->CellInfo.tdscdma.cellIdentityTdscdma.mcc);
+ cellInfoTdscdma.cellIdentityTdscdma.base.base.mnc = ril::util::mnc::decode(
+ rillCellInfo->CellInfo.tdscdma.cellIdentityTdscdma.mnc);
+ cellInfoTdscdma.cellIdentityTdscdma.base.base.lac =
+ rillCellInfo->CellInfo.tdscdma.cellIdentityTdscdma.lac;
+ cellInfoTdscdma.cellIdentityTdscdma.base.base.cid =
+ rillCellInfo->CellInfo.tdscdma.cellIdentityTdscdma.cid;
+ cellInfoTdscdma.cellIdentityTdscdma.base.base.cpid =
+ rillCellInfo->CellInfo.tdscdma.cellIdentityTdscdma.cpid;
+ cellInfoTdscdma.signalStrengthTdscdma.rscp =
+ rillCellInfo->CellInfo.tdscdma.signalStrengthTdscdma.rscp;
+ records[i].ratSpecificInfo.tdscdma(cellInfoTdscdma);
+ break;
+ }
+
+ case RIL_CELL_INFO_TYPE_NR: {
+ V1_5::CellInfoNr cellInfoNr;
+ cellInfoNr.cellIdentityNr.base.mcc =
+ std::to_string(rillCellInfo->CellInfo.nr.cellidentity.mcc);
+ cellInfoNr.cellIdentityNr.base.mnc =
+ ril::util::mnc::decode(rillCellInfo->CellInfo.nr.cellidentity.mnc);
+ cellInfoNr.cellIdentityNr.base.nci = rillCellInfo->CellInfo.nr.cellidentity.nci;
+ cellInfoNr.cellIdentityNr.base.pci = rillCellInfo->CellInfo.nr.cellidentity.pci;
+ cellInfoNr.cellIdentityNr.base.tac = rillCellInfo->CellInfo.nr.cellidentity.tac;
+ cellInfoNr.cellIdentityNr.base.nrarfcn =
+ rillCellInfo->CellInfo.nr.cellidentity.nrarfcn;
+ cellInfoNr.cellIdentityNr.base.operatorNames.alphaLong = convertCharPtrToHidlString(
+ rillCellInfo->CellInfo.nr.cellidentity.operatorNames.alphaLong);
+ cellInfoNr.cellIdentityNr.base.operatorNames.alphaShort =
+ convertCharPtrToHidlString(
+ rillCellInfo->CellInfo.nr.cellidentity.operatorNames.alphaShort);
+
+ cellInfoNr.signalStrengthNr.ssRsrp =
+ rillCellInfo->CellInfo.nr.signalStrength.ssRsrp;
+ cellInfoNr.signalStrengthNr.ssRsrq =
+ rillCellInfo->CellInfo.nr.signalStrength.ssRsrq;
+ cellInfoNr.signalStrengthNr.ssSinr =
+ rillCellInfo->CellInfo.nr.signalStrength.ssSinr;
+ cellInfoNr.signalStrengthNr.csiRsrp =
+ rillCellInfo->CellInfo.nr.signalStrength.csiRsrp;
+ cellInfoNr.signalStrengthNr.csiRsrq =
+ rillCellInfo->CellInfo.nr.signalStrength.csiRsrq;
+ cellInfoNr.signalStrengthNr.csiSinr =
+ rillCellInfo->CellInfo.nr.signalStrength.csiSinr;
+ records[i].ratSpecificInfo.nr(cellInfoNr);
+ break;
+ }
+ default: {
+ break;
+ }
+ }
+ rillCellInfo += 1;
+ }
+}
+
+void convertRilCellInfoListToHal_1_6(void* response, size_t responseLen,
+ hidl_vec<V1_6::CellInfo>& records) {
+ int num = responseLen / sizeof(RIL_CellInfo_v16);
+ records.resize(num);
+ RIL_CellInfo_v16* rillCellInfo = (RIL_CellInfo_v16*)response;
+ for (int i = 0; i < num; i++) {
+ records[i].registered = rillCellInfo->registered;
+ records[i].connectionStatus = (V1_2::CellConnectionStatus)rillCellInfo->connectionStatus;
+
+ switch (rillCellInfo->cellInfoType) {
+ case RIL_CELL_INFO_TYPE_GSM: {
+ V1_5::CellInfoGsm cellInfoGsm;
+ cellInfoGsm.cellIdentityGsm.base.base.mcc =
+ std::to_string(rillCellInfo->CellInfo.gsm.cellIdentityGsm.mcc);
+ cellInfoGsm.cellIdentityGsm.base.base.mnc =
+ ril::util::mnc::decode(rillCellInfo->CellInfo.gsm.cellIdentityGsm.mnc);
+ cellInfoGsm.cellIdentityGsm.base.base.lac =
+ rillCellInfo->CellInfo.gsm.cellIdentityGsm.lac;
+ cellInfoGsm.cellIdentityGsm.base.base.cid =
+ rillCellInfo->CellInfo.gsm.cellIdentityGsm.cid;
+ cellInfoGsm.cellIdentityGsm.base.base.arfcn =
+ rillCellInfo->CellInfo.gsm.cellIdentityGsm.arfcn;
+ cellInfoGsm.cellIdentityGsm.base.base.bsic =
+ rillCellInfo->CellInfo.gsm.cellIdentityGsm.bsic;
+ cellInfoGsm.signalStrengthGsm.signalStrength =
+ rillCellInfo->CellInfo.gsm.signalStrengthGsm.signalStrength;
+ cellInfoGsm.signalStrengthGsm.bitErrorRate =
+ rillCellInfo->CellInfo.gsm.signalStrengthGsm.bitErrorRate;
+ cellInfoGsm.signalStrengthGsm.timingAdvance =
+ rillCellInfo->CellInfo.gsm.signalStrengthGsm.timingAdvance;
+ records[i].ratSpecificInfo.gsm(cellInfoGsm);
+ break;
+ }
+
+ case RIL_CELL_INFO_TYPE_WCDMA: {
+ V1_5::CellInfoWcdma cellInfoWcdma;
+ cellInfoWcdma.cellIdentityWcdma.base.base.mcc =
+ std::to_string(rillCellInfo->CellInfo.wcdma.cellIdentityWcdma.mcc);
+ cellInfoWcdma.cellIdentityWcdma.base.base.mnc =
+ ril::util::mnc::decode(rillCellInfo->CellInfo.wcdma.cellIdentityWcdma.mnc);
+ cellInfoWcdma.cellIdentityWcdma.base.base.lac =
+ rillCellInfo->CellInfo.wcdma.cellIdentityWcdma.lac;
+ cellInfoWcdma.cellIdentityWcdma.base.base.cid =
+ rillCellInfo->CellInfo.wcdma.cellIdentityWcdma.cid;
+ cellInfoWcdma.cellIdentityWcdma.base.base.psc =
+ rillCellInfo->CellInfo.wcdma.cellIdentityWcdma.psc;
+ cellInfoWcdma.cellIdentityWcdma.base.base.uarfcn =
+ rillCellInfo->CellInfo.wcdma.cellIdentityWcdma.uarfcn;
+ cellInfoWcdma.signalStrengthWcdma.base.signalStrength =
+ rillCellInfo->CellInfo.wcdma.signalStrengthWcdma.signalStrength;
+ cellInfoWcdma.signalStrengthWcdma.base.bitErrorRate =
+ rillCellInfo->CellInfo.wcdma.signalStrengthWcdma.bitErrorRate;
+ records[i].ratSpecificInfo.wcdma(cellInfoWcdma);
+ break;
+ }
+
+ case RIL_CELL_INFO_TYPE_CDMA: {
+ V1_2::CellInfoCdma cellInfoCdma;
+ cellInfoCdma.cellIdentityCdma.base.networkId =
+ rillCellInfo->CellInfo.cdma.cellIdentityCdma.networkId;
+ cellInfoCdma.cellIdentityCdma.base.systemId =
+ rillCellInfo->CellInfo.cdma.cellIdentityCdma.systemId;
+ cellInfoCdma.cellIdentityCdma.base.baseStationId =
+ rillCellInfo->CellInfo.cdma.cellIdentityCdma.basestationId;
+ cellInfoCdma.cellIdentityCdma.base.longitude =
+ rillCellInfo->CellInfo.cdma.cellIdentityCdma.longitude;
+ cellInfoCdma.cellIdentityCdma.base.latitude =
+ rillCellInfo->CellInfo.cdma.cellIdentityCdma.latitude;
+ cellInfoCdma.signalStrengthCdma.dbm =
+ rillCellInfo->CellInfo.cdma.signalStrengthCdma.dbm;
+ cellInfoCdma.signalStrengthCdma.ecio =
+ rillCellInfo->CellInfo.cdma.signalStrengthCdma.ecio;
+ cellInfoCdma.signalStrengthEvdo.dbm =
+ rillCellInfo->CellInfo.cdma.signalStrengthEvdo.dbm;
+ cellInfoCdma.signalStrengthEvdo.ecio =
+ rillCellInfo->CellInfo.cdma.signalStrengthEvdo.ecio;
+ cellInfoCdma.signalStrengthEvdo.signalNoiseRatio =
+ rillCellInfo->CellInfo.cdma.signalStrengthEvdo.signalNoiseRatio;
+ records[i].ratSpecificInfo.cdma(cellInfoCdma);
+ break;
+ }
+
+ case RIL_CELL_INFO_TYPE_LTE: {
+ V1_6::CellInfoLte cellInfoLte;
+ cellInfoLte.cellIdentityLte.base.base.mcc =
+ std::to_string(rillCellInfo->CellInfo.lte.cellIdentityLte.mcc);
+ cellInfoLte.cellIdentityLte.base.base.mnc =
+ ril::util::mnc::decode(rillCellInfo->CellInfo.lte.cellIdentityLte.mnc);
+ cellInfoLte.cellIdentityLte.base.base.ci =
+ rillCellInfo->CellInfo.lte.cellIdentityLte.ci;
+ cellInfoLte.cellIdentityLte.base.base.pci =
+ rillCellInfo->CellInfo.lte.cellIdentityLte.pci;
+ cellInfoLte.cellIdentityLte.base.base.tac =
+ rillCellInfo->CellInfo.lte.cellIdentityLte.tac;
+ cellInfoLte.cellIdentityLte.base.base.earfcn =
+ rillCellInfo->CellInfo.lte.cellIdentityLte.earfcn;
+ cellInfoLte.cellIdentityLte.base.bandwidth = INT_MAX;
+ hidl_vec<V1_5::EutranBands> bands;
+ bands.resize(1);
+ bands[0] = V1_5::EutranBands::BAND_1;
+ cellInfoLte.cellIdentityLte.bands = bands;
+ cellInfoLte.signalStrengthLte.base.signalStrength =
+ rillCellInfo->CellInfo.lte.signalStrengthLte.signalStrength;
+ cellInfoLte.signalStrengthLte.base.rsrp =
+ rillCellInfo->CellInfo.lte.signalStrengthLte.rsrp;
+ cellInfoLte.signalStrengthLte.base.rsrq =
+ rillCellInfo->CellInfo.lte.signalStrengthLte.rsrq;
+ cellInfoLte.signalStrengthLte.base.rssnr =
+ rillCellInfo->CellInfo.lte.signalStrengthLte.rssnr;
+ cellInfoLte.signalStrengthLte.base.cqi =
+ rillCellInfo->CellInfo.lte.signalStrengthLte.cqi;
+ cellInfoLte.signalStrengthLte.base.timingAdvance =
+ rillCellInfo->CellInfo.lte.signalStrengthLte.timingAdvance;
+ records[i].ratSpecificInfo.lte(cellInfoLte);
+ break;
+ }
+
+ case RIL_CELL_INFO_TYPE_TD_SCDMA: {
+ V1_5::CellInfoTdscdma cellInfoTdscdma;
+ cellInfoTdscdma.cellIdentityTdscdma.base.base.mcc =
+ std::to_string(rillCellInfo->CellInfo.tdscdma.cellIdentityTdscdma.mcc);
+ cellInfoTdscdma.cellIdentityTdscdma.base.base.mnc = ril::util::mnc::decode(
+ rillCellInfo->CellInfo.tdscdma.cellIdentityTdscdma.mnc);
+ cellInfoTdscdma.cellIdentityTdscdma.base.base.lac =
+ rillCellInfo->CellInfo.tdscdma.cellIdentityTdscdma.lac;
+ cellInfoTdscdma.cellIdentityTdscdma.base.base.cid =
+ rillCellInfo->CellInfo.tdscdma.cellIdentityTdscdma.cid;
+ cellInfoTdscdma.cellIdentityTdscdma.base.base.cpid =
+ rillCellInfo->CellInfo.tdscdma.cellIdentityTdscdma.cpid;
+ cellInfoTdscdma.signalStrengthTdscdma.rscp =
+ rillCellInfo->CellInfo.tdscdma.signalStrengthTdscdma.rscp;
+ records[i].ratSpecificInfo.tdscdma(cellInfoTdscdma);
+ break;
+ }
+
+ case RIL_CELL_INFO_TYPE_NR: {
+ V1_6::CellInfoNr cellInfoNr;
+ cellInfoNr.cellIdentityNr.base.mcc =
+ std::to_string(rillCellInfo->CellInfo.nr.cellidentity.mcc);
+ cellInfoNr.cellIdentityNr.base.mnc =
+ ril::util::mnc::decode(rillCellInfo->CellInfo.nr.cellidentity.mnc);
+ cellInfoNr.cellIdentityNr.base.nci = rillCellInfo->CellInfo.nr.cellidentity.nci;
+ cellInfoNr.cellIdentityNr.base.pci = rillCellInfo->CellInfo.nr.cellidentity.pci;
+ cellInfoNr.cellIdentityNr.base.tac = rillCellInfo->CellInfo.nr.cellidentity.tac;
+ cellInfoNr.cellIdentityNr.base.nrarfcn =
+ rillCellInfo->CellInfo.nr.cellidentity.nrarfcn;
+ cellInfoNr.cellIdentityNr.base.operatorNames.alphaLong = convertCharPtrToHidlString(
+ rillCellInfo->CellInfo.nr.cellidentity.operatorNames.alphaLong);
+ cellInfoNr.cellIdentityNr.base.operatorNames.alphaShort =
+ convertCharPtrToHidlString(
+ rillCellInfo->CellInfo.nr.cellidentity.operatorNames.alphaShort);
+
+ cellInfoNr.signalStrengthNr.base.ssRsrp =
+ rillCellInfo->CellInfo.nr.signalStrength.ssRsrp;
+ cellInfoNr.signalStrengthNr.base.ssRsrq =
+ rillCellInfo->CellInfo.nr.signalStrength.ssRsrq;
+ cellInfoNr.signalStrengthNr.base.ssSinr =
+ rillCellInfo->CellInfo.nr.signalStrength.ssSinr;
+ cellInfoNr.signalStrengthNr.base.csiRsrp =
+ rillCellInfo->CellInfo.nr.signalStrength.csiRsrp;
+ cellInfoNr.signalStrengthNr.base.csiRsrq =
+ rillCellInfo->CellInfo.nr.signalStrength.csiRsrq;
+ cellInfoNr.signalStrengthNr.base.csiSinr =
+ rillCellInfo->CellInfo.nr.signalStrength.csiSinr;
+ records[i].ratSpecificInfo.nr(cellInfoNr);
+ break;
+ }
+ default: {
+ break;
+ }
+ }
+ rillCellInfo += 1;
+ }
+}
+
int radio_1_6::cellInfoListInd(int slotId,
int indicationType, int token, RIL_Errno e, void *response,
size_t responseLen) {
@@ -12426,111 +13112,197 @@
return 0;
}
-int radio_1_6::networkScanResultInd(int slotId,
- int indicationType, int token, RIL_Errno e, void *response,
- size_t responseLen) {
+int radio_1_6::networkScanResultInd(int slotId, int indicationType, int token, RIL_Errno e,
+ void* response, size_t responseLen) {
#if VDBG
RLOGD("networkScanResultInd");
#endif
- if (radioService[slotId] != NULL && radioService[slotId]->mRadioIndicationV1_4 != NULL) {
+ if (radioService[slotId] != NULL && (radioService[slotId]->mRadioIndicationV1_6 != NULL ||
+ radioService[slotId]->mRadioIndicationV1_5 != NULL ||
+ radioService[slotId]->mRadioIndicationV1_4 != NULL ||
+ radioService[slotId]->mRadioIndicationV1_2 != NULL ||
+ radioService[slotId]->mRadioIndicationV1_1 != NULL)) {
if (response == NULL || responseLen == 0) {
RLOGE("networkScanResultInd: invalid response");
return 0;
}
RLOGD("networkScanResultInd");
-#if VDBG
- RLOGD("networkScanResultInd");
-#endif
-
RIL_NetworkScanResult *networkScanResult = (RIL_NetworkScanResult *) response;
-
- V1_1::NetworkScanResult result;
- result.status = (V1_1::ScanStatus) networkScanResult->status;
- result.error = (RadioError) networkScanResult->error;
- convertRilCellInfoListToHal(
- networkScanResult->network_infos,
- networkScanResult->network_infos_length * sizeof(RIL_CellInfo_v12),
- result.networkInfos);
-
- Return<void> retStatus = radioService[slotId]->mRadioIndicationV1_4->networkScanResult(
- convertIntToRadioIndicationType(indicationType), result);
+ Return<void> retStatus;
+ if (radioService[slotId]->mRadioIndicationV1_6 != NULL) {
+ V1_6::NetworkScanResult result;
+ result.status = (V1_1::ScanStatus)networkScanResult->status;
+ result.error = (V1_6::RadioError)networkScanResult->error;
+ convertRilCellInfoListToHal_1_6(
+ networkScanResult->network_infos,
+ networkScanResult->network_infos_length * sizeof(RIL_CellInfo_v16),
+ result.networkInfos);
+ retStatus = radioService[slotId]->mRadioIndicationV1_6->networkScanResult_1_6(
+ convertIntToRadioIndicationType(indicationType), result);
+ } else if (radioService[slotId]->mRadioIndicationV1_5 != NULL) {
+ V1_5::NetworkScanResult result;
+ result.status = (V1_1::ScanStatus)networkScanResult->status;
+ result.error = (RadioError)networkScanResult->error;
+ convertRilCellInfoListToHal_1_5(
+ networkScanResult->network_infos,
+ networkScanResult->network_infos_length * sizeof(RIL_CellInfo_v12),
+ result.networkInfos);
+ retStatus = radioService[slotId]->mRadioIndicationV1_5->networkScanResult_1_5(
+ convertIntToRadioIndicationType(indicationType), result);
+ } else if (radioService[slotId]->mRadioIndicationV1_4 != NULL) {
+ V1_4::NetworkScanResult result;
+ result.status = (V1_1::ScanStatus)networkScanResult->status;
+ result.error = (RadioError)networkScanResult->error;
+ convertRilCellInfoListToHal_1_4(
+ networkScanResult->network_infos,
+ networkScanResult->network_infos_length * sizeof(RIL_CellInfo_v12),
+ result.networkInfos);
+ retStatus = radioService[slotId]->mRadioIndicationV1_4->networkScanResult_1_4(
+ convertIntToRadioIndicationType(indicationType), result);
+ } else if (radioService[slotId]->mRadioIndicationV1_2 != NULL) {
+ V1_2::NetworkScanResult result;
+ result.status = (V1_1::ScanStatus)networkScanResult->status;
+ result.error = (RadioError)networkScanResult->error;
+ convertRilCellInfoListToHal_1_2(
+ networkScanResult->network_infos,
+ networkScanResult->network_infos_length * sizeof(RIL_CellInfo_v12),
+ result.networkInfos);
+ retStatus = radioService[slotId]->mRadioIndicationV1_2->networkScanResult_1_2(
+ convertIntToRadioIndicationType(indicationType), result);
+ } else {
+ V1_1::NetworkScanResult result;
+ result.status = (V1_1::ScanStatus)networkScanResult->status;
+ result.error = (RadioError)networkScanResult->error;
+ convertRilCellInfoListToHal(
+ networkScanResult->network_infos,
+ networkScanResult->network_infos_length * sizeof(RIL_CellInfo),
+ result.networkInfos);
+ retStatus = radioService[slotId]->mRadioIndicationV1_1->networkScanResult(
+ convertIntToRadioIndicationType(indicationType), result);
+ }
radioService[slotId]->checkReturnStatus(retStatus);
} else {
- RLOGE("networkScanResultInd: radioService[%d]->mRadioIndicationV1_4 == NULL", slotId);
+ RLOGE("networkScanResultInd: radioService[%d]->mRadioIndication == NULL", slotId);
}
return 0;
}
-int radio_1_6::carrierInfoForImsiEncryption(int slotId,
- int indicationType, int token, RIL_Errno e, void *response,
- size_t responseLen) {
- if (radioService[slotId] != NULL && radioService[slotId]->mRadioIndicationV1_4 != NULL) {
+int radio_1_6::carrierInfoForImsiEncryption(int slotId, int indicationType, int token, RIL_Errno e,
+ void* response, size_t responseLen) {
+ if (radioService[slotId] != NULL && (radioService[slotId]->mRadioIndicationV1_2 != NULL)) {
if (response == NULL || responseLen == 0) {
RLOGE("carrierInfoForImsiEncryption: invalid response");
return 0;
}
RLOGD("carrierInfoForImsiEncryption");
- Return<void> retStatus = radioService[slotId]->mRadioIndicationV1_4->
- carrierInfoForImsiEncryption(convertIntToRadioIndicationType(indicationType));
+ Return<void> retStatus =
+ radioService[slotId]->mRadioIndicationV1_2->carrierInfoForImsiEncryption(
+ convertIntToRadioIndicationType(indicationType));
radioService[slotId]->checkReturnStatus(retStatus);
} else {
- RLOGE("carrierInfoForImsiEncryption: radioService[%d]->mRadioIndicationV1_4 == NULL",
- slotId);
+ RLOGE("carrierInfoForImsiEncryption: radioService[%d]->mRadioIndication == NULL", slotId);
}
return 0;
}
-int radio_1_6::reportPhysicalChannelConfigs(int slotId, int indicationType,
- int token, RIL_Errno e,
- void *response,
- size_t responseLen) {
- if (radioService[slotId] != NULL &&
- radioService[slotId]->mRadioIndicationV1_4 != NULL) {
- int *configs = (int *)response;
- ::android::hardware::hidl_vec<PhysicalChannelConfigV1_4> physChanConfig;
- physChanConfig.resize(1);
- physChanConfig[0].base.status =
- (::android::hardware::radio::V1_2::CellConnectionStatus)configs[0];
- physChanConfig[0].base.cellBandwidthDownlink = configs[1];
- physChanConfig[0].rat =
- (::android::hardware::radio::V1_4::RadioTechnology)configs[2];
- physChanConfig[0].rfInfo.range(
- (::android::hardware::radio::V1_4::FrequencyRange)configs[3]);
- physChanConfig[0].contextIds.resize(1);
- physChanConfig[0].contextIds[0] = configs[4];
- RLOGD("reportPhysicalChannelConfigs: %d %d %d %d %d", configs[0],
- configs[1], configs[2], configs[3], configs[4]);
- Return<void> retStatus = radioService[slotId]
- ->mRadioIndicationV1_4->currentPhysicalChannelConfigs_1_4(
- RadioIndicationType::UNSOLICITED, physChanConfig);
- radioService[slotId]->checkReturnStatus(retStatus);
- {
- // just send the link estimate along with physical channel
- // config, as it has at least the downlink bandwidth.
- // Note: the bandwidth is just some hardcoded
- // value, as there is not way to get that reliably on
- // virtual devices, as of now.
- V1_2::LinkCapacityEstimate lce = {
- .downlinkCapacityKbps = static_cast<uint32_t>(configs[1]),
- .uplinkCapacityKbps = static_cast<uint32_t>(configs[1])
- };
- RLOGD("reporting link capacity estimate download: %d upload: %d",
- lce.downlinkCapacityKbps, lce.uplinkCapacityKbps );
- Return<void> retStatus = radioService[slotId]->mRadioIndicationV1_4->
- currentLinkCapacityEstimate(RadioIndicationType::UNSOLICITED, lce);
- radioService[slotId]->checkReturnStatus(retStatus);
- }
+int radio_1_6::reportPhysicalChannelConfigs(int slotId, int indicationType, int token, RIL_Errno e,
+ void* response, size_t responseLen) {
+ if (radioService[slotId] != NULL && (radioService[slotId]->mRadioIndicationV1_6 != NULL ||
+ radioService[slotId]->mRadioIndicationV1_4 != NULL ||
+ radioService[slotId]->mRadioIndicationV1_2 != NULL)) {
+ int* configs = (int*)response;
+ if (radioService[slotId]->mRadioIndicationV1_6 != NULL) {
+ hidl_vec<V1_6::PhysicalChannelConfig> physChanConfig;
+ physChanConfig.resize(1);
+ physChanConfig[0].status = (V1_2::CellConnectionStatus)configs[0];
+ physChanConfig[0].cellBandwidthDownlinkKhz = configs[1];
+ physChanConfig[0].rat = (V1_4::RadioTechnology)configs[2];
+ physChanConfig[0].contextIds.resize(1);
+ physChanConfig[0].contextIds[0] = configs[4];
+ RLOGD("reportPhysicalChannelConfigs_1_6: %d %d %d %d %d", configs[0], configs[1],
+ configs[2], configs[3], configs[4]);
+ Return<void> retStatus =
+ radioService[slotId]->mRadioIndicationV1_6->currentPhysicalChannelConfigs_1_6(
+ RadioIndicationType::UNSOLICITED, physChanConfig);
+ radioService[slotId]->checkReturnStatus(retStatus);
+ {
+ // Just send the link estimate along with physical channel config, as it has
+ // at least the downlink bandwidth.
+ // Note: the bandwidth is just some hardcoded value, as there is not way to get
+ // that reliably on virtual devices, as of now.
+ V1_6::LinkCapacityEstimate lce = {
+ .downlinkCapacityKbps = static_cast<uint32_t>(configs[1]),
+ .uplinkCapacityKbps = static_cast<uint32_t>(configs[1])};
+ RLOGD("reporting link capacity estimate download: %d upload: %d",
+ lce.downlinkCapacityKbps, lce.uplinkCapacityKbps);
+ Return<void> retStatus =
+ radioService[slotId]->mRadioIndicationV1_6->currentLinkCapacityEstimate_1_6(
+ RadioIndicationType::UNSOLICITED, lce);
+ radioService[slotId]->checkReturnStatus(retStatus);
+ }
+ } else if (radioService[slotId]->mRadioIndicationV1_4 != NULL) {
+ hidl_vec<PhysicalChannelConfigV1_4> physChanConfig;
+ physChanConfig.resize(1);
+ physChanConfig[0].base.status = (V1_2::CellConnectionStatus)configs[0];
+ physChanConfig[0].base.cellBandwidthDownlink = configs[1];
+ physChanConfig[0].rat = (V1_4::RadioTechnology)configs[2];
+ physChanConfig[0].rfInfo.range((V1_4::FrequencyRange)configs[3]);
+ physChanConfig[0].contextIds.resize(1);
+ physChanConfig[0].contextIds[0] = configs[4];
+ RLOGD("reportPhysicalChannelConfigs_1_4: %d %d %d %d %d", configs[0], configs[1],
+ configs[2], configs[3], configs[4]);
+ Return<void> retStatus =
+ radioService[slotId]->mRadioIndicationV1_4->currentPhysicalChannelConfigs_1_4(
+ RadioIndicationType::UNSOLICITED, physChanConfig);
+ radioService[slotId]->checkReturnStatus(retStatus);
+ {
+ // Just send the link estimate along with physical channel config, as it has
+ // at least the downlink bandwidth.
+ // Note: the bandwidth is just some hardcoded value, as there is not way to get
+ // that reliably on virtual devices, as of now.
+ V1_2::LinkCapacityEstimate lce = {
+ .downlinkCapacityKbps = static_cast<uint32_t>(configs[1]),
+ .uplinkCapacityKbps = static_cast<uint32_t>(configs[1])};
+ RLOGD("reporting link capacity estimate download: %d upload: %d",
+ lce.downlinkCapacityKbps, lce.uplinkCapacityKbps);
+ Return<void> retStatus =
+ radioService[slotId]->mRadioIndicationV1_4->currentLinkCapacityEstimate(
+ RadioIndicationType::UNSOLICITED, lce);
+ radioService[slotId]->checkReturnStatus(retStatus);
+ }
+ } else {
+ hidl_vec<V1_2::PhysicalChannelConfig> physChanConfig;
+ physChanConfig.resize(1);
+ physChanConfig[0].status = (V1_2::CellConnectionStatus)configs[0];
+ physChanConfig[0].cellBandwidthDownlink = configs[1];
+ RLOGD("reportPhysicalChannelConfigs_1_2: %d %d", configs[0], configs[1]);
+ Return<void> retStatus =
+ radioService[slotId]->mRadioIndicationV1_2->currentPhysicalChannelConfigs(
+ RadioIndicationType::UNSOLICITED, physChanConfig);
+ radioService[slotId]->checkReturnStatus(retStatus);
+ {
+ // Just send the link estimate along with physical channel config, as it has
+ // at least the downlink bandwidth.
+ // Note: the bandwidth is just some hardcoded value, as there is not way to get
+ // that reliably on virtual devices, as of now.
+ V1_2::LinkCapacityEstimate lce = {
+ .downlinkCapacityKbps = static_cast<uint32_t>(configs[1]),
+ .uplinkCapacityKbps = static_cast<uint32_t>(configs[1])};
+ RLOGD("reporting link capacity estimate download: %d upload: %d",
+ lce.downlinkCapacityKbps, lce.uplinkCapacityKbps);
+ Return<void> retStatus =
+ radioService[slotId]->mRadioIndicationV1_2->currentLinkCapacityEstimate(
+ RadioIndicationType::UNSOLICITED, lce);
+ radioService[slotId]->checkReturnStatus(retStatus);
+ }
+ }
} else {
- RLOGE(
- "reportPhysicalChannelConfigs: radioService[%d]->mRadioIndicationV1_4 "
- "== NULL",
- slotId);
- return -1;
+ RLOGE("reportPhysicalChannelConfigs: radioService[%d]->mRadioIndication == NULL", slotId);
+ return -1;
}
-
- return 0;
+ return 0;
}
int radio_1_6::keepaliveStatusInd(int slotId,
@@ -12592,6 +13364,20 @@
return 0;
}
+template <typename T>
+static void publishRadioHal(std::shared_ptr<compat::DriverContext> ctx, sp<V1_5::IRadio> hidlHal,
+ std::shared_ptr<compat::CallbackManager> cm, const std::string& slot) {
+ static std::vector<std::shared_ptr<ndk::ICInterface>> gPublishedHals;
+
+ const auto instance = T::descriptor + "/"s + slot;
+ RLOGD("Publishing %s", instance.c_str());
+
+ auto aidlHal = ndk::SharedRefBase::make<T>(ctx, hidlHal, cm);
+ gPublishedHals.push_back(aidlHal);
+ const auto status = AServiceManager_addService(aidlHal->asBinder().get(), instance.c_str());
+ CHECK_EQ(status, STATUS_OK);
+}
+
void radio_1_6::registerService(RIL_RadioFunctions *callbacks, CommandInfo *commands) {
using namespace android::hardware;
int simCount = 1;
@@ -12615,11 +13401,10 @@
s_vendorFunctions = callbacks;
s_commands = commands;
- configureRpcThreadpool(1, true /* callerWillJoin */);
for (int i = 0; i < simCount; i++) {
pthread_rwlock_t *radioServiceRwlockPtr = getRadioServiceRwlock(i);
int ret = pthread_rwlock_wrlock(radioServiceRwlockPtr);
- assert(ret == 0);
+ CHECK_EQ(ret, 0);
RLOGD("sim i = %d registering ...", i);
@@ -12629,8 +13414,19 @@
radioService[i]->mSimCardPowerState = V1_1::CardPowerState::POWER_UP;
RLOGD("registerService: starting android::hardware::radio::V1_6::IRadio %s for slot %d",
serviceNames[i], i);
- android::status_t status = radioService[i]->registerAsService(serviceNames[i]);
- LOG_ALWAYS_FATAL_IF(status != android::OK, "status %d", status);
+
+ // use a compat shim to convert HIDL interface to AIDL and publish it
+ // PLEASE NOTE this is a temporary solution
+ auto radioHidl = radioService[i];
+ const auto slot = serviceNames[i];
+ auto context = std::make_shared<compat::DriverContext>();
+ auto callbackMgr = std::make_shared<compat::CallbackManager>(context, radioHidl);
+ publishRadioHal<compat::RadioData>(context, radioHidl, callbackMgr, slot);
+ publishRadioHal<compat::RadioMessaging>(context, radioHidl, callbackMgr, slot);
+ publishRadioHal<compat::RadioModem>(context, radioHidl, callbackMgr, slot);
+ publishRadioHal<cf::ril::RefRadioNetwork>(context, radioHidl, callbackMgr, slot);
+ publishRadioHal<compat::RadioSim>(context, radioHidl, callbackMgr, slot);
+ publishRadioHal<compat::RadioVoice>(context, radioHidl, callbackMgr, slot);
RLOGD("registerService: OemHook is enabled = %s", kOemHookEnabled ? "true" : "false");
if (kOemHookEnabled) {
@@ -12640,12 +13436,12 @@
}
ret = pthread_rwlock_unlock(radioServiceRwlockPtr);
- assert(ret == 0);
+ CHECK_EQ(ret, 0);
}
}
void rilc_thread_pool() {
- joinRpcThreadpool();
+ ABinderProcess_joinThreadPool();
}
pthread_rwlock_t * radio_1_6::getRadioServiceRwlock(int slotId) {
diff --git a/guest/hals/ril/reference-ril/atchannel.c b/guest/hals/ril/reference-ril/atchannel.c
index 6ea051f..2e2d8b5 100644
--- a/guest/hals/ril/reference-ril/atchannel.c
+++ b/guest/hals/ril/reference-ril/atchannel.c
@@ -897,22 +897,10 @@
{
int i;
int err = 0;
- bool inEmulator;
- if (0 != pthread_equal(s_tid_reader, pthread_self())) {
- /* cannot be called from reader thread */
- return AT_ERROR_INVALID_THREAD;
- }
- inEmulator = isInEmulator();
- if (inEmulator) {
- pthread_mutex_lock(&s_writeMutex);
- }
- pthread_mutex_lock(&s_commandmutex);
-
- for (i = 0 ; i < HANDSHAKE_RETRY_COUNT ; i++) {
+ for (i = 0; i < HANDSHAKE_RETRY_COUNT && err != AT_ERROR_INVALID_THREAD; i++) {
/* some stacks start with verbose off */
- err = at_send_command_full_nolock ("ATE0Q0V1", NO_RESULT,
- NULL, NULL, HANDSHAKE_TIMEOUT_MSEC, NULL);
+ err = at_send_command_full("ATE0Q0V1", NO_RESULT, NULL, NULL, HANDSHAKE_TIMEOUT_MSEC, NULL);
if (err == 0) {
break;
@@ -926,11 +914,6 @@
sleepMsec(HANDSHAKE_TIMEOUT_MSEC);
}
- pthread_mutex_unlock(&s_commandmutex);
- if (inEmulator) {
- pthread_mutex_unlock(&s_writeMutex);
- }
-
return err;
}
diff --git a/guest/hals/ril/reference-ril/reference-ril.c b/guest/hals/ril/reference-ril/reference-ril.c
index 5d17ab4..f3d01e4 100644
--- a/guest/hals/ril/reference-ril/reference-ril.c
+++ b/guest/hals/ril/reference-ril/reference-ril.c
@@ -54,12 +54,12 @@
#define MAX_AT_RESPONSE 0x1000
-#define MAX_PDP 3
+#define MAX_PDP 11 // max LTE bearers
/* pathname returned from RIL_REQUEST_SETUP_DATA_CALL / RIL_REQUEST_SETUP_DEFAULT_PDP */
// This is used if Wifi is not supported, plain old eth0
#ifdef CUTTLEFISH_ENABLE
-#define PPP_TTY_PATH_ETH0 "rmnet0"
+#define PPP_TTY_PATH_ETH0 "buried_eth0"
#else
#define PPP_TTY_PATH_ETH0 "eth0"
#endif
@@ -208,7 +208,7 @@
#define CDMA (RAF_IS95A | RAF_IS95B | RAF_1xRTT)
#define EVDO (RAF_EVDO_0 | RAF_EVDO_A | RAF_EVDO_B | RAF_EHRPD)
#define WCDMA (RAF_HSUPA | RAF_HSDPA | RAF_HSPA | RAF_HSPAP | RAF_UMTS)
-#define LTE (RAF_LTE | RAF_LTE_CA)
+#define LTE (RAF_LTE)
#define NR (RAF_NR)
typedef struct {
@@ -413,9 +413,8 @@
};
struct PDPInfo s_PDP[] = {
- {1, PDP_IDLE},
- {2, PDP_IDLE},
- {3, PDP_IDLE},
+ {1, PDP_IDLE}, {2, PDP_IDLE}, {3, PDP_IDLE}, {4, PDP_IDLE}, {5, PDP_IDLE}, {6, PDP_IDLE},
+ {7, PDP_IDLE}, {8, PDP_IDLE}, {9, PDP_IDLE}, {10, PDP_IDLE}, {11, PDP_IDLE},
};
static void pollSIMState (void *param);
@@ -704,11 +703,10 @@
{
int onOff;
- int err;
ATResponse *p_response = NULL;
if (sState != RADIO_STATE_OFF) {
- err = at_send_command("AT+CFUN=0", &p_response);
+ at_send_command("AT+CFUN=0", &p_response);
setRadioState(RADIO_STATE_UNAVAILABLE);
}
@@ -717,6 +715,18 @@
return;
}
+static void requestNvResetConfig(void* data, size_t datalen __unused, RIL_Token t) {
+ assert(datalen >= sizeof(int*));
+ int nvConfig = ((int*)data)[0];
+ if (nvConfig == 1 /* ResetNvType::RELOAD */) {
+ setRadioState(RADIO_STATE_OFF);
+ // Wait for FW to process radio off before sending radio on for reboot
+ sleep(5);
+ setRadioState(RADIO_STATE_ON);
+ }
+ RIL_onRequestComplete(t, RIL_E_SUCCESS, NULL, 0);
+}
+
static void requestOrSendDataCallList(int cid, RIL_Token *t);
static void onDataCallListChanged(void *param __unused)
@@ -963,6 +973,19 @@
}
}
+ // If cid = -1, return the data call list without processing CGCONTRDP (setupDataCall)
+ if (cid == -1) {
+ if (t != NULL)
+ RIL_onRequestComplete(*t, RIL_E_SUCCESS, &responses[0],
+ sizeof(RIL_Data_Call_Response_v11));
+ else
+ RIL_onUnsolicitedResponse(RIL_UNSOL_DATA_CALL_LIST_CHANGED, responses,
+ n * sizeof(RIL_Data_Call_Response_v11));
+ at_response_free(p_response);
+ p_response = NULL;
+ return;
+ }
+
at_response_free(p_response);
p_response = NULL;
@@ -1197,7 +1220,6 @@
RIL_Dial *p_dial;
char *cmd;
const char *clir;
- int ret;
p_dial = (RIL_Dial *)data;
@@ -1210,7 +1232,7 @@
asprintf(&cmd, "ATD%s%s;", p_dial->address, clir);
- ret = at_send_command(cmd, NULL);
+ at_send_command(cmd, NULL);
free(cmd);
@@ -1254,7 +1276,6 @@
{
int *p_line;
- int ret;
char *cmd;
if (getSIMStatus() == SIM_ABSENT) {
@@ -1267,7 +1288,7 @@
// "Releases a specific active call X"
asprintf(&cmd, "AT+CHLD=1%d", p_line[0]);
- ret = at_send_command(cmd, NULL);
+ at_send_command(cmd, NULL);
free(cmd);
@@ -1289,6 +1310,7 @@
memset(response, 0, sizeof(response));
+ // TODO(b/206814247): Rename AT+CSQ command.
err = at_send_command_singleline("AT+CSQ", "+CSQ:", &p_response);
if (err < 0 || p_response->success == 0) {
@@ -1449,6 +1471,32 @@
RIL_onRequestComplete(t, RIL_E_SUCCESS, &i, sizeof(i));
}
+static void requestGetCarrierRestrictions(void* data, size_t datalen, RIL_Token t) {
+ RIL_UNUSED_PARM(datalen);
+ RIL_UNUSED_PARM(data);
+
+ // Fixed values. TODO: query modem
+ RIL_Carrier allowed_carriers = {
+ "123", // mcc
+ "456", // mnc
+ RIL_MATCH_ALL, // match_type
+ "", // match_data
+ };
+
+ RIL_Carrier excluded_carriers;
+
+ RIL_CarrierRestrictionsWithPriority restrictions = {
+ 1, // len_allowed_carriers
+ 0, // len_excluded_carriers
+ &allowed_carriers, // allowed_carriers
+ &excluded_carriers, // excluded_carriers
+ 1, // allowedCarriersPrioritized
+ NO_MULTISIM_POLICY // multiSimPolicy
+ };
+
+ RIL_onRequestComplete(t, RIL_E_SUCCESS, &restrictions, sizeof(restrictions));
+}
+
static void requestCdmaPrlVersion(int request __unused, void *data __unused,
size_t datalen __unused, RIL_Token t)
{
@@ -1672,9 +1720,11 @@
char *line = str, *p;
int *resp = NULL;
int skip;
- int count = 3;
int commas;
+ s_lac = -1;
+ s_cid = -1;
+
RLOGD("parseRegistrationState. Parsing: %s",str);
err = at_tok_start(&line);
if (err < 0) goto error;
@@ -1712,19 +1762,14 @@
case 0: /* +CREG: <stat> */
err = at_tok_nextint(&line, &resp[0]);
if (err < 0) goto error;
- resp[1] = -1;
- resp[2] = -1;
- break;
+ break;
case 1: /* +CREG: <n>, <stat> */
err = at_tok_nextint(&line, &skip);
if (err < 0) goto error;
err = at_tok_nextint(&line, &resp[0]);
if (err < 0) goto error;
- resp[1] = -1;
- resp[2] = -1;
- if (err < 0) goto error;
- break;
+ break;
case 2: /* +CREG: <stat>, <lac>, <cid> */
err = at_tok_nextint(&line, &resp[0]);
@@ -1758,13 +1803,16 @@
if (err < 0) goto error;
err = at_tok_nextint(&line, &resp[3]);
if (err < 0) goto error;
- count = 4;
break;
default:
goto error;
}
- s_lac = resp[1];
- s_cid = resp[2];
+
+ if (commas >= 2) {
+ s_lac = resp[1];
+ s_cid = resp[2];
+ }
+
if (response)
*response = resp;
if (items)
@@ -1893,8 +1941,12 @@
} else { // type == RADIO_TECH_3GPP
RLOGD("registration state type: 3GPP");
startfrom = 0;
- asprintf(&responseStr[1], "%x", registration[1]);
- asprintf(&responseStr[2], "%x", registration[2]);
+ if (count > 1) {
+ asprintf(&responseStr[1], "%x", registration[1]);
+ }
+ if (count > 2) {
+ asprintf(&responseStr[2], "%x", registration[2]);
+ }
if (count > 3) {
asprintf(&responseStr[3], "%d", mapNetworkRegistrationResponse(registration[3]));
}
@@ -2615,7 +2667,6 @@
RIL_UNUSED_PARM(datalen);
int err, len;
- int instruction = 0;
char *cmd = NULL;
char *line = NULL;
RIL_SIM_APDU *p_args = NULL;
@@ -2660,7 +2711,6 @@
sscanf(&(sr.simResponse[len - 4]), "%02x%02x", &(sr.sw1), &(sr.sw2));
sr.simResponse[len - 4] = '\0';
- instruction = p_args->instruction;
RIL_onRequestComplete(t, RIL_E_SUCCESS, &sr, sizeof(sr));
at_response_free(p_response);
return;
@@ -2765,9 +2815,9 @@
goto error;
}
- qmistatus = system("netcfg rmnet0 dhcp");
+ qmistatus = system("netcfg buried_eth0 dhcp");
- RLOGD("netcfg rmnet0 dhcp: status %d\n", qmistatus);
+ RLOGD("netcfg buried_eth0 dhcp: status %d\n", qmistatus);
if (qmistatus < 0) goto error;
@@ -2784,7 +2834,24 @@
}
cid = getPDP();
- if (cid < 1 ) goto error;
+ if (cid < 1) {
+ RLOGE("SETUP_DATA_CALL MAX_PDP reached.");
+ RIL_Data_Call_Response_v11 response;
+ response.status = 0x41 /* PDP_FAIL_MAX_ACTIVE_PDP_CONTEXT_REACHED */;
+ response.suggestedRetryTime = -1;
+ response.cid = cid;
+ response.active = -1;
+ response.type = "";
+ response.ifname = "";
+ response.addresses = "";
+ response.dnses = "";
+ response.gateways = "";
+ response.pcscf = "";
+ response.mtu = 0;
+ RIL_onRequestComplete(t, RIL_E_SUCCESS, &response, sizeof(RIL_Data_Call_Response_v11));
+ at_response_free(p_response);
+ return;
+ }
asprintf(&cmd, "AT+CGDCONT=%d,\"%s\",\"%s\",,0,0", cid, pdp_type, apn);
//FIXME check for error here
@@ -2835,6 +2902,7 @@
rilErrno = setInterfaceState(radioInterfaceName, kInterfaceDown);
RIL_onRequestComplete(t, rilErrno, NULL, 0);
putPDP(cid);
+ requestOrSendDataCallList(-1, NULL);
}
static void requestSMSAcknowledge(void *data, size_t datalen __unused, RIL_Token t)
@@ -2851,14 +2919,22 @@
if (ackSuccess == 1) {
err = at_send_command("AT+CNMA=1", NULL);
+ if (err < 0) {
+ goto error;
+ }
} else if (ackSuccess == 0) {
err = at_send_command("AT+CNMA=2", NULL);
+ if (err < 0) {
+ goto error;
+ }
} else {
RLOGE("unsupported arg to RIL_REQUEST_SMS_ACKNOWLEDGE\n");
goto error;
}
RIL_onRequestComplete(t, RIL_E_SUCCESS, NULL, 0);
+
+ return;
error:
RIL_onRequestComplete(t, RIL_E_GENERIC_FAILURE, NULL, 0);
}
@@ -3264,6 +3340,36 @@
RIL_onRequestComplete(t, RIL_E_SUCCESS, ci, sizeof(ci));
}
+static void requestGetCellInfoList_1_6(void* data __unused, size_t datalen __unused, RIL_Token t) {
+ RIL_CellInfo_v16 ci[1] = {{ // ci[0]
+ 3, // cellInfoType
+ 1, // registered
+ CELL_CONNECTION_PRIMARY_SERVING,
+ { // union CellInfo
+ .lte = {// RIL_CellInfoLte_v12 lte
+ {
+ // RIL_CellIdentityLte_v12
+ // lte.cellIdentityLte
+ s_mcc, // mcc
+ s_mnc, // mnc
+ s_cid, // ci
+ 0, // pci
+ s_lac, // tac
+ 7, // earfcn
+ },
+ {
+ // RIL_LTE_SignalStrength_v8
+ // lte.signalStrengthLte
+ 10, // signalStrength
+ 44, // rsrp
+ 3, // rsrq
+ 30, // rssnr
+ 0, // cqi
+ INT_MAX // timingAdvance invalid value
+ }}}}};
+
+ RIL_onRequestComplete(t, RIL_E_SUCCESS, ci, sizeof(ci));
+}
static void requestSetCellInfoListRate(void *data, size_t datalen __unused, RIL_Token t)
{
@@ -4670,6 +4776,10 @@
requestGetCellInfoList(data, datalen, t);
break;
+ case RIL_REQUEST_GET_CELL_INFO_LIST_1_6:
+ requestGetCellInfoList_1_6(data, datalen, t);
+ break;
+
case RIL_REQUEST_SET_UNSOL_CELL_INFO_LIST_RATE:
requestSetCellInfoListRate(data, datalen, t);
break;
@@ -4716,6 +4826,7 @@
case RIL_REQUEST_ALLOW_DATA:
case RIL_REQUEST_ENTER_NETWORK_DEPERSONALIZATION:
case RIL_REQUEST_SET_BAND_MODE:
+ case RIL_REQUEST_SET_CARRIER_RESTRICTIONS:
case RIL_REQUEST_GET_NEIGHBORING_CELL_IDS:
case RIL_REQUEST_SET_LOCATION_UPDATES:
case RIL_REQUEST_SET_TTY_MODE:
@@ -4723,6 +4834,10 @@
RIL_onRequestComplete(t, RIL_E_SUCCESS, NULL, 0);
break;
+ case RIL_REQUEST_NV_RESET_CONFIG:
+ requestNvResetConfig(data, datalen, t);
+ break;
+
case RIL_REQUEST_BASEBAND_VERSION:
requestCdmaBaseBandVersion(request, data, datalen, t);
break;
@@ -4922,6 +5037,8 @@
case RIL_REQUEST_GET_SLICING_CONFIG:
RIL_onRequestComplete(t, RIL_E_SUCCESS, NULL, 0);
break;
+ case RIL_REQUEST_GET_CARRIER_RESTRICTIONS:
+ requestGetCarrierRestrictions(data, datalen, t);
// Radio config requests
case RIL_REQUEST_CONFIG_GET_SLOT_STATUS:
@@ -5004,6 +5121,9 @@
case RIL_REQUEST_STOP_KEEPALIVE:
RIL_onRequestComplete(t, RIL_E_SUCCESS, NULL, 0);
break;
+ case RIL_REQUEST_SET_UNSOLICITED_RESPONSE_FILTER:
+ RIL_onRequestComplete(t, RIL_E_SUCCESS, NULL, 0);
+ break;
default:
RLOGD("Request not supported. Tech: %d",TECH(sMdmInfo));
RIL_onRequestComplete(t, RIL_E_REQUEST_NOT_SUPPORTED, NULL, 0);
@@ -6018,7 +6138,7 @@
response, sizeof(response));
free(line);
} else if (strStartsWith(s, "+CUSATEND")) { // session end
- RIL_onUnsolicitedResponse(RIL_UNSOL_STK_SESSION_END, NULL, 0);
+ RIL_onUnsolicitedResponse(RIL_UNSOL_STK_SESSION_END, NULL, 0);
} else if (strStartsWith(s, "+CUSATP:")) {
line = p = strdup(s);
if (!line) {
@@ -6235,6 +6355,10 @@
pthread_attr_init (&attr);
pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
ret = pthread_create(&s_tid_mainloop, &attr, mainLoop, NULL);
+ if (ret < 0) {
+ RLOGE("pthread_create: %s:", strerror(errno));
+ return NULL;
+ }
return &s_callbacks;
}
diff --git a/host/commands/tapsetiff/Android.bp b/guest/hals/wpa_supplicant/Android.bp
similarity index 65%
copy from host/commands/tapsetiff/Android.bp
copy to guest/hals/wpa_supplicant/Android.bp
index 1d7dedb..888b6e0 100644
--- a/host/commands/tapsetiff/Android.bp
+++ b/guest/hals/wpa_supplicant/Android.bp
@@ -1,5 +1,4 @@
-//
-// Copyright (C) 2020 The Android Open Source Project
+// Copyright (C) 2021 The Android Open Source Project
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
@@ -14,10 +13,15 @@
// limitations under the License.
package {
- default_applicable_licenses: ["Android-Apache-2.0"],
+ default_applicable_licenses: [
+ "external_wpa_supplicant_8_license",
+ ],
}
-sh_binary_host {
- name: "tapsetiff",
- src: "tapsetiff.py",
+cc_binary {
+ name: "wpa_supplicant_cf",
+ defaults: ["wpa_supplicant_defaults"],
+ static_libs: [
+ "lib_driver_cmd_simulated_cf_bp",
+ ],
}
diff --git a/guest/libs/wpa_supplicant_8_lib/Android.bp b/guest/libs/wpa_supplicant_8_lib/Android.bp
new file mode 100644
index 0000000..d09457e
--- /dev/null
+++ b/guest/libs/wpa_supplicant_8_lib/Android.bp
@@ -0,0 +1,18 @@
+package {
+ default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+cc_library_static {
+ name: "lib_driver_cmd_simulated_cf_bp",
+ srcs: ["driver_cmd_nl80211.c"],
+ cflags: ["-DCONFIG_ANDROID_LOG"],
+ header_libs: [
+ "wpa_supplicant_headers",
+ ],
+ shared_libs: [
+ "libc",
+ "libcutils",
+ "libnl",
+ ],
+ soc_specific: true,
+}
diff --git a/guest/monitoring/cuttlefish_service/Android.bp b/guest/monitoring/cuttlefish_service/Android.bp
index 133d233..d33d0e7 100644
--- a/guest/monitoring/cuttlefish_service/Android.bp
+++ b/guest/monitoring/cuttlefish_service/Android.bp
@@ -20,6 +20,9 @@
name: "CuttlefishService",
vendor: true,
srcs: ["java/**/*.java"],
+ resource_dirs: [
+ "res",
+ ],
static_libs: ["guava"],
sdk_version: "28",
privileged: true,
diff --git a/guest/monitoring/cuttlefish_service/java/com/android/google/gce/gceservice/BluetoothChecker.java b/guest/monitoring/cuttlefish_service/java/com/android/google/gce/gceservice/BluetoothChecker.java
index 23d4fec..000a96d 100644
--- a/guest/monitoring/cuttlefish_service/java/com/android/google/gce/gceservice/BluetoothChecker.java
+++ b/guest/monitoring/cuttlefish_service/java/com/android/google/gce/gceservice/BluetoothChecker.java
@@ -16,6 +16,8 @@
package com.android.google.gce.gceservice;
import android.bluetooth.BluetoothAdapter;
+import android.content.Context;
+import android.content.res.Resources;
import android.util.Log;
/*
@@ -28,8 +30,13 @@
private final GceFuture<Boolean> mEnabled = new GceFuture<Boolean>("Bluetooth");
- public BluetoothChecker() {
+ public BluetoothChecker(Context context) {
super(LOG_TAG);
+ Resources res = context.getResources();
+ if (!res.getBoolean(R.bool.config_bt_required)) {
+ Log.i(LOG_TAG, "Bluetooth not required, assuming enabled.");
+ mEnabled.set(true);
+ }
}
diff --git a/guest/monitoring/cuttlefish_service/java/com/android/google/gce/gceservice/GceService.java b/guest/monitoring/cuttlefish_service/java/com/android/google/gce/gceservice/GceService.java
index 80d497a..ef0aa03 100644
--- a/guest/monitoring/cuttlefish_service/java/com/android/google/gce/gceservice/GceService.java
+++ b/guest/monitoring/cuttlefish_service/java/com/android/google/gce/gceservice/GceService.java
@@ -51,8 +51,8 @@
private final JobExecutor mExecutor = new JobExecutor();
private final EventReporter mEventReporter = new EventReporter();
private final GceBroadcastReceiver mBroadcastReceiver = new GceBroadcastReceiver();
- private final BluetoothChecker mBluetoothChecker = new BluetoothChecker();
+ private BluetoothChecker mBluetoothChecker = null;
private ConnectivityChecker mConnChecker;
private GceWifiManager mWifiManager = null;
private String mMostRecentAction = null;
@@ -76,6 +76,7 @@
mWindowManager = getSystemService(WindowManager.class);
mConnChecker = new ConnectivityChecker(this, mEventReporter);
mWifiManager = new GceWifiManager(this, mEventReporter, mExecutor);
+ mBluetoothChecker = new BluetoothChecker(this);
mPreviousRotation = getRotation();
mPreviousScreenBounds = getScreenBounds();
diff --git a/guest/monitoring/cuttlefish_service/res/values/config.xml b/guest/monitoring/cuttlefish_service/res/values/config.xml
new file mode 100644
index 0000000..692a686
--- /dev/null
+++ b/guest/monitoring/cuttlefish_service/res/values/config.xml
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2020 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<resources>
+ <!-- Is BT required for boot complete -->
+ <bool name="config_bt_required">true</bool>
+</resources>
diff --git a/guest/monitoring/tombstone_transmit/Android.bp b/guest/monitoring/tombstone_transmit/Android.bp
index d24985a..149274c 100644
--- a/guest/monitoring/tombstone_transmit/Android.bp
+++ b/guest/monitoring/tombstone_transmit/Android.bp
@@ -17,13 +17,9 @@
default_applicable_licenses: ["Android-Apache-2.0"],
}
-cc_binary {
- name: "tombstone_transmit",
- srcs: [
- "tombstone_transmit.cpp",
- ],
+cc_defaults {
+ name: "tombstone_transmit_defaults",
static_libs: [
- "libcuttlefish_fs_product",
"libgflags",
"libbase",
"libcutils",
@@ -32,7 +28,36 @@
"liblog",
],
stl: "libc++_static",
- defaults: ["cuttlefish_guest_product_only"],
+}
+
+cc_binary {
+ name: "tombstone_transmit",
+ srcs: [
+ "tombstone_transmit.cpp",
+ ],
+ static_libs: [
+ "libcuttlefish_fs_product",
+ ],
+ defaults: [
+ "tombstone_transmit_defaults",
+ "cuttlefish_guest_product_only"
+ ],
+}
+
+// Same as tombstone_transmit, but place the binary in /system partition
+// We are using the binary for microdroid, which does not have /product partition
+cc_binary {
+ name: "tombstone_transmit.microdroid",
+ srcs: [
+ "tombstone_transmit.cpp",
+ ],
+ static_libs: [
+ "libcuttlefish_fs",
+ ],
+ defaults: [
+ "tombstone_transmit_defaults",
+ "cuttlefish_base",
+ ],
}
cc_binary {
diff --git a/guest/monitoring/tombstone_transmit/tombstone_transmit.cpp b/guest/monitoring/tombstone_transmit/tombstone_transmit.cpp
index 3d08780..0853b14 100644
--- a/guest/monitoring/tombstone_transmit/tombstone_transmit.cpp
+++ b/guest/monitoring/tombstone_transmit/tombstone_transmit.cpp
@@ -55,31 +55,40 @@
}
#define INOTIFY_MAX_EVENT_SIZE (sizeof(struct inotify_event) + NAME_MAX + 1)
-static std::string get_next_tombstone_path_blocking(int fd) {
+static std::vector<std::string> get_next_tombstones_path_blocking(int fd) {
char event_readout[INOTIFY_MAX_EVENT_SIZE];
- struct inotify_event *i = (inotify_event*)(event_readout);
-
+ int bytes_parsed = 0;
+ std::vector<std::string> tombstone_paths;
+ // Each successful read can contain one or more of inotify_event events
+ // Note: read() on inotify returns 'whole' events, will never partially
+ // populate the buffer.
int event_read_out_length = read(fd, event_readout, INOTIFY_MAX_EVENT_SIZE);
if(event_read_out_length == -1) {
ALOGE("%s: Couldn't read out inotify event due to error: '%s' (%d)",
__FUNCTION__, strerror(errno), errno);
- return std::string();
+ return std::vector<std::string>();
}
- // Create event didn't show up for some reason or no file name was present
- if(event_read_out_length == sizeof(struct inotify_event)) {
- ALOGE("%s: inotify event didn't contain filename",__FUNCTION__);
- return std::string();
+ while (bytes_parsed < event_read_out_length) {
+ struct inotify_event* event =
+ reinterpret_cast<inotify_event*>(event_readout + bytes_parsed);
+ bytes_parsed += sizeof(struct inotify_event) + event->len;
+
+ // No file name was present
+ if (event->len == 0) {
+ ALOGE("%s: inotify event didn't contain filename", __FUNCTION__);
+ continue;
+ }
+ if (!(event->mask & IN_CREATE)) {
+ ALOGE("%s: inotify event didn't pertain to file creation", __FUNCTION__);
+ continue;
+ }
+ tombstone_paths.push_back(std::string(TOMBSTONE_DIR) +
+ std::string(event->name));
}
- if(!(i->mask & IN_CREATE)) {
- ALOGE("%s: inotify event didn't pertain to file creation",__FUNCTION__);
- return std::string();
- }
-
- std::string ret_value(TOMBSTONE_DIR);
- return ret_value + i->name;
+ return tombstone_paths;
}
DEFINE_uint32(port,
@@ -103,34 +112,33 @@
LOG(INFO) << "tombstone watcher successfully initialized";
while (true) {
- std::string ts_path = get_next_tombstone_path_blocking(
- file_create_notification_handle);
+ std::vector<std::string> ts_paths =
+ get_next_tombstones_path_blocking(file_create_notification_handle);
+ for (auto& ts_path : ts_paths) {
+ auto log_fd =
+ cuttlefish::SharedFD::VsockClient(FLAGS_cid, FLAGS_port, SOCK_STREAM);
+ std::ifstream ifs(ts_path);
+ char buffer[TOMBSTONE_BUFFER_SIZE];
+ uint num_transfers = 0;
+ int num_bytes_read = 0;
+ while (log_fd->IsOpen() && ifs.is_open() && !ifs.eof()) {
+ ifs.read(buffer, sizeof(buffer));
+ num_bytes_read += ifs.gcount();
+ log_fd->Write(buffer, ifs.gcount());
+ num_transfers++;
+ }
- if(ts_path.empty()) {continue;}
-
- auto log_fd = cuttlefish::SharedFD::VsockClient(FLAGS_cid, FLAGS_port,
- SOCK_STREAM);
-
- std::ifstream ifs(ts_path);
- char buffer[TOMBSTONE_BUFFER_SIZE];
- uint num_transfers = 0;
- int num_bytes_read = 0;
- while (log_fd->IsOpen() && ifs.is_open() && !ifs.eof()) {
- ifs.read(buffer, sizeof(buffer));
- num_bytes_read += ifs.gcount();
- log_fd->Write(buffer, ifs.gcount());
- num_transfers++;
- }
-
- if (!log_fd->IsOpen()) {
- ALOGE("Unable to connect to vsock:%u:%u: %s", FLAGS_cid, FLAGS_port,
- log_fd->StrError());
- } else if (!ifs.is_open()) {
- ALOGE("%s closed in the middle of readout.", ts_path.c_str());
- } else {
- LOG(INFO) << num_bytes_read << " chars transferred from " <<
- ts_path.c_str() << " over " << num_transfers << " "
- << TOMBSTONE_BUFFER_SIZE << " byte sized transfers";
+ if (!log_fd->IsOpen()) {
+ auto error = log_fd->StrError();
+ ALOGE("Unable to connect to vsock:%u:%u: %s", FLAGS_cid, FLAGS_port,
+ error.c_str());
+ } else if (!ifs.is_open()) {
+ ALOGE("%s closed in the middle of readout.", ts_path.c_str());
+ } else {
+ LOG(INFO) << num_bytes_read << " chars transferred from "
+ << ts_path.c_str() << " over " << num_transfers << " "
+ << TOMBSTONE_BUFFER_SIZE << " byte sized transfers";
+ }
}
}
diff --git a/guest/services/wifi/Android.bp b/guest/services/wifi/Android.bp
new file mode 100644
index 0000000..4a5fd53
--- /dev/null
+++ b/guest/services/wifi/Android.bp
@@ -0,0 +1,44 @@
+// Copyright (C) 2021 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package {
+ default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+genrule {
+ name: "init.wifi.sh_apex_srcs",
+ srcs: ["init.wifi.sh"],
+ out: ["init.wifi.sh_apex"],
+ cmd: "sed -E 's/\\/vendor\\/bin\\/mac802/\\/apex\\/com.android.wifi.hal\\/bin\\/mac802/' $(in) > $(out)",
+}
+
+sh_binary {
+ name: "init.wifi.sh_apex",
+ src: ":init.wifi.sh_apex_srcs",
+ filename: "init.wifi.sh",
+ vendor: true,
+ installable: false,
+ init_rc: [
+ "init.wifi.sh.rc",
+ ],
+}
+
+sh_binary {
+ name: "init.wifi.sh",
+ src: "init.wifi.sh",
+ vendor: true,
+ init_rc: [
+ "init.wifi.sh.rc",
+ ],
+}
diff --git a/guest/services/wifi/init.wifi.sh b/guest/services/wifi/init.wifi.sh
new file mode 100755
index 0000000..a23f174
--- /dev/null
+++ b/guest/services/wifi/init.wifi.sh
@@ -0,0 +1,7 @@
+#!/vendor/bin/sh
+
+wifi_mac_prefix=`getprop ro.boot.wifi_mac_prefix`
+if [ -n "$wifi_mac_prefix" ]; then
+ /vendor/bin/mac80211_create_radios 2 $wifi_mac_prefix || exit 1
+fi
+
diff --git a/guest/services/wifi/init.wifi.sh.rc b/guest/services/wifi/init.wifi.sh.rc
new file mode 100644
index 0000000..9a0daee
--- /dev/null
+++ b/guest/services/wifi/init.wifi.sh.rc
@@ -0,0 +1,8 @@
+
+service init_wifi_sh /vendor/bin/init.wifi.sh
+ class late_start
+ user root
+ group root wakelock wifi
+ oneshot
+ disabled # Started on post-fs-data
+
diff --git a/host/commands/adbshell/main.cpp b/host/commands/adbshell/main.cpp
deleted file mode 100644
index 7263f4c..0000000
--- a/host/commands/adbshell/main.cpp
+++ /dev/null
@@ -1,121 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-/* Utility that uses an adb connection as the login shell. */
-
-#include <array>
-#include <cassert>
-#include <cstdio>
-#include <cstdlib>
-#include <cstring>
-#include <string>
-#include <vector>
-
-#include <errno.h>
-#include <unistd.h>
-
-#include "common/libs/utils/environment.h"
-#include "host/libs/config/cuttlefish_config.h"
-
-// Many of our users interact with CVDs via ssh. They expect to be able to
-// get an Android shell (as opposed to the host shell) with a single command.
-//
-// Our goals are to:
-//
-// * Allow the user to select which CVD to connect to
-//
-// * Avoid modifications to the host-side sshd and the protocol
-//
-// We accomplish this by using specialized accounts: vsoc-## and cvd-## and
-// specific Android serial numbers:
-//
-// The vsoc-01 account provides a host-side shell that controls the first CVD
-// The cvd-01 account is connected to the Andorid shell of the first CVD
-// The first CVD has a serial number of CUTTLEFISHCVD01
-//
-// The code in the commands/launch directory also follows these conventions by
-// default.
-//
-
-namespace {
-std::string VsocUser() {
- std::string user = cuttlefish::StringFromEnv("USER", "");
- assert(!user_cstring.empty());
-
- std::string cvd_prefix = "cvd-";
- if (user.find(cvd_prefix) == 0) {
- user.replace(0, cvd_prefix.size(), cuttlefish::kVsocUserPrefix);
- }
- return user;
-}
-
-std::string CuttlefishConfigLocation() {
- return std::string("/home/") + VsocUser() +
- "/cuttlefish_runtime/cuttlefish_config.json";
-}
-
-std::string CuttlefishFindAdb() {
- std::string rval = std::string("/home/") + VsocUser() + "/bin/adb";
- if (TEMP_FAILURE_RETRY(access(rval.c_str(), X_OK)) == -1) {
- return "/usr/bin/adb";
- }
- return rval;
-}
-
-void SetCuttlefishConfigEnv() {
- setenv(cuttlefish::kCuttlefishConfigEnvVarName, CuttlefishConfigLocation().c_str(),
- true);
-}
-} // namespace
-
-int main(int argc, char* argv[]) {
- SetCuttlefishConfigEnv();
- auto instance = cuttlefish::CuttlefishConfig::Get()
- ->ForDefaultInstance().adb_device_name();
- std::string adb_path = CuttlefishFindAdb();
-
- std::vector<char*> new_argv = {
- const_cast<char*>(adb_path.c_str()), const_cast<char*>("-s"),
- const_cast<char*>(instance.c_str()), const_cast<char*>("shell"),
- const_cast<char*>("/system/bin/sh")};
-
- // Some important data is lost before this point, and there are
- // no great recovery options:
- // * ssh with no arguments comes in with 1 arg of -adbshell. The command
- // given above does the right thing if we don't invoke the shell.
- if (argc == 1) {
- new_argv.back() = nullptr;
- }
- // * simple shell commands come in with a -c and a single string. The
- // problem here is that adb doesn't preserve spaces, so we need
- // to do additional escaping. The best compromise seems to be to
- // throw double quotes around each string.
- for (int i = 1; i < argc; ++i) {
- size_t buf_size = std::strlen(argv[i]) + 4;
- new_argv.push_back(new char[buf_size]);
- std::snprintf(new_argv.back(), buf_size, "\"%s\"", argv[i]);
- }
- //
- // * scp seems to be pathologically broken when paths contain spaces.
- // spaces aren't properly escaped by gcloud, so scp will fail with
- // "scp: with ambiguous target." We might be able to fix this with
- // some creative parsing of the arguments, but that seems like
- // overkill.
- new_argv.push_back(nullptr);
- execv(new_argv[0], new_argv.data());
- // This never should happen
- return 2;
-}
diff --git a/host/commands/append_squashfs_overlay/Android.bp b/host/commands/append_squashfs_overlay/Android.bp
new file mode 100644
index 0000000..882bcb9
--- /dev/null
+++ b/host/commands/append_squashfs_overlay/Android.bp
@@ -0,0 +1,12 @@
+package {
+ default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+rust_binary_host {
+ name: "append_squashfs_overlay",
+ crate_name: "append_squashfs_overlay",
+ srcs: ["src/main.rs"],
+ rustlibs: [
+ "libclap",
+ ],
+}
\ No newline at end of file
diff --git a/host/commands/append_squashfs_overlay/Cargo.toml b/host/commands/append_squashfs_overlay/Cargo.toml
new file mode 100644
index 0000000..5472580
--- /dev/null
+++ b/host/commands/append_squashfs_overlay/Cargo.toml
@@ -0,0 +1,8 @@
+[package]
+name = "append_squashfs_overlay"
+version = "0.1.0"
+authors = ["Jeongik Cha <[email protected]>"]
+edition = 2021
+
+[dependencies]
+clap = "2.33"
\ No newline at end of file
diff --git a/host/commands/append_squashfs_overlay/OWNERS b/host/commands/append_squashfs_overlay/OWNERS
new file mode 100644
index 0000000..0930c9e
--- /dev/null
+++ b/host/commands/append_squashfs_overlay/OWNERS
@@ -0,0 +1 @@
[email protected]
\ No newline at end of file
diff --git a/host/commands/append_squashfs_overlay/src/main.rs b/host/commands/append_squashfs_overlay/src/main.rs
new file mode 100644
index 0000000..281aef8
--- /dev/null
+++ b/host/commands/append_squashfs_overlay/src/main.rs
@@ -0,0 +1,95 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+//! `append_squashfs_overlay` generates a new squashfs image(dest) which contains an overlay image(overlay) on an squashfs image(src).
+//! The tool ignores the existing overlay image in src, that is, the overlay image could be replaced with a new overlay image.
+use std::fs::File;
+use std::io::{copy, Error, ErrorKind, Read, Result, Seek, SeekFrom};
+use std::path::Path;
+
+use clap::{App, Arg};
+
+// https://dr-emann.github.io/squashfs/squashfs.html
+const BYTES_USED_FIELD_POS: u64 = (32 * 5 + 16 * 6 + 64) / 8;
+const SQUASHFS_MAGIC: u32 = 0x73717368;
+
+// https://git.openwrt.org/?p=project/fstools.git;a=blob;f=libfstools/rootdisk.c;h=9f2317f14e8d8f12c71b30944138d7a6c877b406;hb=refs/heads/master#l125
+// 64kb alignment
+const ROOTDEV_OVERLAY_ALIGN: u64 = 64 * 1024;
+
+fn align_size(size: u64, alignment: u64) -> u64 {
+ assert!(
+ alignment > 0 && (alignment & (alignment - 1) == 0),
+ "alignment should be greater than 0 and a power of 2."
+ );
+ (size + (alignment - 1)) & !(alignment - 1)
+}
+
+fn merge_fs(src: &Path, overlay: &Path, dest: &Path, overwrite: bool) -> Result<()> {
+ if dest.exists() && !overwrite {
+ return Err(Error::new(
+ ErrorKind::AlreadyExists,
+ "The destination file already exists, add -w option to overwrite.",
+ ));
+ }
+ let mut buffer = [0; 4];
+
+ let mut src = File::open(src)?;
+
+ src.read_exact(&mut buffer)?;
+ let magic = u32::from_le_bytes(buffer);
+ if magic != SQUASHFS_MAGIC {
+ return Err(Error::new(ErrorKind::InvalidData, "The source image isn't a squashfs image."));
+ }
+ src.seek(SeekFrom::Start(BYTES_USED_FIELD_POS))?;
+ let mut buffer = [0; 8];
+ src.read_exact(&mut buffer)?;
+
+ // https://git.openwrt.org/?p=project/fstools.git;a=blob;f=libfstools/rootdisk.c;h=9f2317f14e8d8f12c71b30944138d7a6c877b406;hb=refs/heads/master#l125
+ // use little endian
+ let bytes_used = u64::from_le_bytes(buffer);
+ let mut dest = File::create(dest)?;
+ let mut overlay = File::open(overlay)?;
+
+ src.seek(SeekFrom::Start(0))?;
+ let mut src_handle = src.take(align_size(bytes_used, ROOTDEV_OVERLAY_ALIGN));
+ copy(&mut src_handle, &mut dest)?;
+ copy(&mut overlay, &mut dest)?;
+ Ok(())
+}
+
+fn main() -> Result<()> {
+ let matches = App::new("append_squashfs_overlay")
+ .arg(Arg::with_name("src").required(true))
+ .arg(Arg::with_name("overlay").required(true))
+ .arg(Arg::with_name("dest").required(true))
+ .arg(
+ Arg::with_name("overwrite")
+ .short("w")
+ .required(false)
+ .takes_value(false)
+ .help("whether the tool overwrite dest or not"),
+ )
+ .get_matches();
+
+ let src = matches.value_of("src").unwrap().as_ref();
+ let overlay = matches.value_of("overlay").unwrap().as_ref();
+ let dest = matches.value_of("dest").unwrap().as_ref();
+ let overwrite = matches.is_present("overwrite");
+
+ merge_fs(src, overlay, dest, overwrite)?;
+ Ok(())
+}
diff --git a/host/commands/assemble_cvd/Android.bp b/host/commands/assemble_cvd/Android.bp
index 4ed4452..a2d4ac3 100644
--- a/host/commands/assemble_cvd/Android.bp
+++ b/host/commands/assemble_cvd/Android.bp
@@ -25,8 +25,10 @@
"boot_config.cc",
"boot_image_utils.cc",
"clean.cc",
+ "disk_builder.cpp",
"disk_flags.cc",
"flags.cc",
+ "flag_feature.cpp",
"misc_info.cc",
"super_image_mixer.cc",
],
@@ -34,9 +36,11 @@
"bootimg_headers",
],
shared_libs: [
+ "libext2_blkid",
"libcuttlefish_fs",
"libcuttlefish_utils",
"libbase",
+ "libfruit",
"libjsoncpp",
"libnl",
"libprotobuf-cpp-full",
@@ -51,10 +55,12 @@
"libsparse",
"libcuttlefish_graphics_detector",
"libcuttlefish_host_config",
+ "libcuttlefish_host_config_adb",
"libcuttlefish_vm_manager",
"libgflags",
],
required: [
+ "mkenvimage_slim",
"lz4",
],
defaults: ["cuttlefish_host", "cuttlefish_libicuuc"],
diff --git a/host/commands/assemble_cvd/assemble_cvd.cc b/host/commands/assemble_cvd/assemble_cvd.cc
index 27eadb5..70607fa 100644
--- a/host/commands/assemble_cvd/assemble_cvd.cc
+++ b/host/commands/assemble_cvd/assemble_cvd.cc
@@ -23,18 +23,24 @@
#include "common/libs/fs/shared_fd.h"
#include "common/libs/utils/environment.h"
#include "common/libs/utils/files.h"
+#include "common/libs/utils/flag_parser.h"
#include "common/libs/utils/tee_logging.h"
#include "host/commands/assemble_cvd/clean.h"
#include "host/commands/assemble_cvd/disk_flags.h"
+#include "host/commands/assemble_cvd/flag_feature.h"
#include "host/commands/assemble_cvd/flags.h"
+#include "host/libs/config/adb/adb.h"
+#include "host/libs/config/config_flag.h"
+#include "host/libs/config/custom_actions.h"
#include "host/libs/config/fetcher_config.h"
using cuttlefish::StringFromEnv;
DEFINE_string(assembly_dir, StringFromEnv("HOME", ".") + "/cuttlefish_assembly",
"A directory to put generated files common between instances");
-DEFINE_string(instance_dir, StringFromEnv("HOME", ".") + "/cuttlefish_runtime",
- "A directory to put all instance specific files");
+DEFINE_string(instance_dir, StringFromEnv("HOME", ".") + "/cuttlefish",
+ "This is a directory that will hold the cuttlefish generated"
+ "files, including both instance-specific and common files");
DEFINE_bool(resume, true, "Resume using the disk from the last session, if "
"possible. i.e., if --noresume is passed, the disk "
"will be reset to the state it was initially launched "
@@ -65,55 +71,78 @@
return config.ForDefaultInstance().PerInstancePath("cuttlefish_config.json");
}
-bool SaveConfig(const CuttlefishConfig& tmp_config_obj) {
+Result<void> SaveConfig(const CuttlefishConfig& tmp_config_obj) {
auto config_file = GetConfigFilePath(tmp_config_obj);
auto config_link = GetGlobalConfigFileLink();
// Save the config object before starting any host process
- if (!tmp_config_obj.SaveToFile(config_file)) {
- LOG(ERROR) << "Unable to save config object";
- return false;
- }
+ CF_EXPECT(tmp_config_obj.SaveToFile(config_file),
+ "Failed to save to \"" << config_file << "\"");
auto legacy_config_file = GetLegacyConfigFilePath(tmp_config_obj);
- if (!tmp_config_obj.SaveToFile(legacy_config_file)) {
- LOG(ERROR) << "Unable to save legacy config object";
- return false;
- }
+ CF_EXPECT(tmp_config_obj.SaveToFile(legacy_config_file),
+ "Failed to save to \"" << legacy_config_file << "\"");
+
setenv(kCuttlefishConfigEnvVarName, config_file.c_str(), true);
if (symlink(config_file.c_str(), config_link.c_str()) != 0) {
- LOG(ERROR) << "Failed to create symlink to config file at " << config_link
- << ": " << strerror(errno);
- return false;
+ return CF_ERRNO("symlink(\"" << config_file << "\", \"" << config_link
+ << ") failed");
}
- return true;
-}
-
-void ValidateAdbModeFlag(const CuttlefishConfig& config) {
- auto adb_modes = config.adb_mode();
- adb_modes.erase(AdbMode::Unknown);
- if (adb_modes.size() < 1) {
- LOG(INFO) << "ADB not enabled";
- }
+ return {};
}
#ifndef O_TMPFILE
# define O_TMPFILE (020000000 | O_DIRECTORY)
#endif
-const CuttlefishConfig* InitFilesystemAndCreateConfig(
- FetcherConfig fetcher_config, KernelConfig kernel_config) {
- std::string assembly_dir_parent = AbsolutePath(FLAGS_assembly_dir);
- while (assembly_dir_parent[assembly_dir_parent.size() - 1] == '/') {
- assembly_dir_parent =
- assembly_dir_parent.substr(0, FLAGS_assembly_dir.rfind('/'));
+Result<void> CreateLegacySymlinks(
+ const CuttlefishConfig::InstanceSpecific& instance) {
+ std::string log_files[] = {
+ "kernel.log", "launcher.log", "logcat",
+ "metrics.log", "modem_simulator.log", "crosvm_openwrt.log",
+ };
+ for (const auto& log_file : log_files) {
+ auto symlink_location = instance.PerInstancePath(log_file.c_str());
+ auto log_target = "logs/" + log_file; // Relative path
+ if (symlink(log_target.c_str(), symlink_location.c_str()) != 0) {
+ return CF_ERRNO("symlink(\"" << log_target << ", " << symlink_location
+ << ") failed");
+ }
}
- assembly_dir_parent =
- assembly_dir_parent.substr(0, FLAGS_assembly_dir.rfind('/'));
- auto log =
- SharedFD::Open(
- assembly_dir_parent,
- O_WRONLY | O_TMPFILE,
- S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP);
+
+ std::stringstream legacy_instance_path_stream;
+ legacy_instance_path_stream << FLAGS_instance_dir;
+ if (gflags::GetCommandLineFlagInfoOrDie("instance_dir").is_default) {
+ legacy_instance_path_stream << "_runtime";
+ }
+ legacy_instance_path_stream << "." << instance.id();
+ auto legacy_instance_path = legacy_instance_path_stream.str();
+
+ if (DirectoryExists(legacy_instance_path, /* follow_symlinks */ false)) {
+ CF_EXPECT(RecursivelyRemoveDirectory(legacy_instance_path),
+ "Failed to remove legacy directory " << legacy_instance_path);
+ } else if (FileExists(legacy_instance_path, /* follow_symlinks */ false)) {
+ CF_EXPECT(RemoveFile(legacy_instance_path),
+ "Failed to remove instance_dir symlink " << legacy_instance_path);
+ }
+ if (symlink(instance.instance_dir().c_str(), legacy_instance_path.c_str())) {
+ return CF_ERRNO("symlink(\"" << instance.instance_dir() << "\", \""
+ << legacy_instance_path << "\") failed");
+ }
+ return {};
+}
+
+Result<const CuttlefishConfig*> InitFilesystemAndCreateConfig(
+ FetcherConfig fetcher_config, KernelConfig kernel_config,
+ fruit::Injector<>& injector) {
+ std::string runtime_dir_parent = AbsolutePath(FLAGS_instance_dir);
+ while (runtime_dir_parent[runtime_dir_parent.size() - 1] == '/') {
+ runtime_dir_parent =
+ runtime_dir_parent.substr(0, FLAGS_instance_dir.rfind('/'));
+ }
+ runtime_dir_parent =
+ runtime_dir_parent.substr(0, FLAGS_instance_dir.rfind('/'));
+ auto log = SharedFD::Open(runtime_dir_parent, O_WRONLY | O_TMPFILE,
+ S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP);
if (!log->IsOpen()) {
LOG(ERROR) << "Could not open O_TMPFILE precursor to assemble_cvd.log: "
<< log->StrError();
@@ -129,15 +158,19 @@
// SaveConfig line below. Don't launch cuttlefish subprocesses between these
// two operations, as those will assume they can read the config object from
// disk.
- auto config = InitializeCuttlefishConfiguration(
- FLAGS_instance_dir, FLAGS_modem_simulator_count, kernel_config);
+ auto config = InitializeCuttlefishConfiguration(FLAGS_instance_dir,
+ FLAGS_modem_simulator_count,
+ kernel_config, injector);
std::set<std::string> preserving;
- if (FLAGS_resume && ShouldCreateAllCompositeDisks(config)) {
+ auto os_builder = OsCompositeDiskBuilder(config);
+ bool creating_os_disk = CF_EXPECT(os_builder.WillRebuildCompositeDisk());
+ if (FLAGS_resume && creating_os_disk) {
LOG(INFO) << "Requested resuming a previous session (the default behavior) "
<< "but the base images have changed under the overlay, making the "
<< "overlay incompatible. Wiping the overlay files.";
- } else if (FLAGS_resume && !ShouldCreateAllCompositeDisks(config)) {
+ } else if (FLAGS_resume && !creating_os_disk) {
preserving.insert("overlay.img");
+ preserving.insert("ap_overlay.img");
preserving.insert("os_composite_disk_config.txt");
preserving.insert("os_composite_gpt_header.img");
preserving.insert("os_composite_gpt_footer.img");
@@ -146,6 +179,7 @@
preserving.insert("boot_repacked.img");
preserving.insert("vendor_boot_repacked.img");
preserving.insert("access-kregistry");
+ preserving.insert("hwcomposer-pmem");
preserving.insert("NVChip");
preserving.insert("gatekeeper_secure");
preserving.insert("gatekeeper_insecure");
@@ -165,40 +199,74 @@
ss.str("");
}
}
- CHECK(CleanPriorFiles(preserving, FLAGS_assembly_dir, FLAGS_instance_dir))
- << "Failed to clean prior files";
+ CF_EXPECT(CleanPriorFiles(preserving, config.assembly_dir(),
+ config.instance_dirs()),
+ "Failed to clean prior files");
- // Create assembly directory if it doesn't exist.
- CHECK(EnsureDirectoryExists(FLAGS_assembly_dir));
+ CF_EXPECT(EnsureDirectoryExists(config.root_dir()));
+ CF_EXPECT(EnsureDirectoryExists(config.assembly_dir()));
+ CF_EXPECT(EnsureDirectoryExists(config.instances_dir()));
if (log->LinkAtCwd(config.AssemblyPath("assemble_cvd.log"))) {
LOG(ERROR) << "Unable to persist assemble_cvd log at "
<< config.AssemblyPath("assemble_cvd.log")
<< ": " << log->StrError();
}
+
+ auto disk_config = GetOsCompositeDiskConfig();
+ if (auto it = std::find_if(disk_config.begin(), disk_config.end(),
+ [](const auto& partition) {
+ return partition.label == "ap_rootfs";
+ });
+ it != disk_config.end()) {
+ auto ap_image_idx = std::distance(disk_config.begin(), it) + 1;
+ std::stringstream ss;
+ ss << "/dev/vda" << ap_image_idx;
+ config.set_ap_image_dev_path(ss.str());
+ }
for (const auto& instance : config.Instances()) {
// Create instance directory if it doesn't exist.
- CHECK(EnsureDirectoryExists(instance.instance_dir()));
+ CF_EXPECT(EnsureDirectoryExists(instance.instance_dir()));
auto internal_dir = instance.instance_dir() + "/" + kInternalDirName;
- CHECK(EnsureDirectoryExists(internal_dir));
+ CF_EXPECT(EnsureDirectoryExists(internal_dir));
auto shared_dir = instance.instance_dir() + "/" + kSharedDirName;
- CHECK(EnsureDirectoryExists(shared_dir));
+ CF_EXPECT(EnsureDirectoryExists(shared_dir));
auto recording_dir = instance.instance_dir() + "/recording";
- CHECK(EnsureDirectoryExists(recording_dir));
+ CF_EXPECT(EnsureDirectoryExists(recording_dir));
+ CF_EXPECT(EnsureDirectoryExists(instance.PerInstanceLogPath("")));
+ // TODO(schuffelen): Move this code somewhere better
+ CF_EXPECT(CreateLegacySymlinks(instance));
}
- CHECK(SaveConfig(config)) << "Failed to initialize configuration";
+ CF_EXPECT(SaveConfig(config), "Failed to initialize configuration");
}
- std::string first_instance = FLAGS_instance_dir + "." + std::to_string(GetInstance());
- CHECK_EQ(symlink(first_instance.c_str(), FLAGS_instance_dir.c_str()), 0)
- << "Could not symlink \"" << first_instance << "\" to \"" << FLAGS_instance_dir << "\"";
-
// Do this early so that the config object is ready for anything that needs it
auto config = CuttlefishConfig::Get();
- CHECK(config) << "Failed to obtain config singleton";
+ CF_EXPECT(config != nullptr, "Failed to obtain config singleton");
- ValidateAdbModeFlag(*config);
+ if (DirectoryExists(FLAGS_assembly_dir, /* follow_symlinks */ false)) {
+ CF_EXPECT(RecursivelyRemoveDirectory(FLAGS_assembly_dir),
+ "Failed to remove directory " << FLAGS_assembly_dir);
+ } else if (FileExists(FLAGS_assembly_dir, /* follow_symlinks */ false)) {
+ CF_EXPECT(RemoveFile(FLAGS_assembly_dir),
+ "Failed to remove file" << FLAGS_assembly_dir);
+ }
+ if (symlink(config->assembly_dir().c_str(), FLAGS_assembly_dir.c_str())) {
+ return CF_ERRNO("symlink(\"" << config->assembly_dir() << "\", \""
+ << FLAGS_assembly_dir << "\") failed");
+ }
- CreateDynamicDiskFiles(fetcher_config, config);
+ std::string first_instance = config->Instances()[0].instance_dir();
+ std::string double_legacy_instance_dir = FLAGS_instance_dir + "_runtime";
+ if (FileExists(double_legacy_instance_dir, /* follow_symlinks */ false)) {
+ CF_EXPECT(RemoveFile(double_legacy_instance_dir),
+ "Failed to remove symlink " << double_legacy_instance_dir);
+ }
+ if (symlink(first_instance.c_str(), double_legacy_instance_dir.c_str())) {
+ return CF_ERRNO("symlink(\"" << first_instance << "\", \""
+ << double_legacy_instance_dir << "\") failed");
+ }
+
+ CF_EXPECT(CreateDynamicDiskFiles(fetcher_config, *config));
return config;
}
@@ -218,27 +286,38 @@
SetCommandLineOptionWithMode("initramfs_path", discovered_ramdisk.c_str(),
google::FlagSettingMode::SET_FLAGS_DEFAULT);
}
+
+fruit::Component<> FlagsComponent() {
+ return fruit::createComponent()
+ .install(AdbConfigComponent)
+ .install(AdbConfigFlagComponent)
+ .install(AdbConfigFragmentComponent)
+ .install(GflagsComponent)
+ .install(ConfigFlagComponent)
+ .install(CustomActionsComponent);
+}
+
} // namespace
-int AssembleCvdMain(int argc, char** argv) {
+Result<int> AssembleCvdMain(int argc, char** argv) {
setenv("ANDROID_LOG_TAGS", "*:v", /* overwrite */ 0);
::android::base::InitLogging(argv, android::base::StderrLogger);
int tty = isatty(0);
int error_num = errno;
- CHECK_EQ(tty, 0)
- << "stdin was a tty, expected to be passed the output of a previous stage. "
- << "Did you mean to run launch_cvd?";
- CHECK(error_num != EBADF)
- << "stdin was not a valid file descriptor, expected to be passed the output "
- << "of launch_cvd. Did you mean to run launch_cvd?";
+ CF_EXPECT(tty == 0,
+ "stdin was a tty, expected to be passed the output of a "
+ "previous stage. Did you mean to run launch_cvd?");
+ CF_EXPECT(error_num != EBADF,
+ "stdin was not a valid file descriptor, expected to be "
+ "passed the output of launch_cvd. Did you mean to run launch_cvd?");
std::string input_files_str;
{
auto input_fd = SharedFD::Dup(0);
auto bytes_read = ReadAll(input_fd, &input_files_str);
- CHECK(bytes_read >= 0)
- << "Failed to read input files. Error was \"" << input_fd->StrError() << "\"";
+ CF_EXPECT(bytes_read >= 0, "Failed to read input files. Error was \""
+ << input_fd->StrError() << "\"");
}
std::vector<std::string> input_files = android::base::Split(input_files_str, "\n");
@@ -246,11 +325,54 @@
// set gflags defaults to point to kernel/RD from fetcher config
ExtractKernelParamsFromFetcherConfig(fetcher_config);
- KernelConfig kernel_config;
- CHECK(ParseCommandLineFlags(&argc, &argv, &kernel_config)) << "Failed to parse arguments";
+ auto args = ArgsToVec(argc - 1, argv + 1);
+
+ bool help = false;
+ std::string help_str;
+ bool helpxml = false;
+
+ std::vector<Flag> help_flags = {
+ GflagsCompatFlag("help", help),
+ GflagsCompatFlag("helpfull", help),
+ GflagsCompatFlag("helpshort", help),
+ GflagsCompatFlag("helpmatch", help_str),
+ GflagsCompatFlag("helpon", help_str),
+ GflagsCompatFlag("helppackage", help_str),
+ GflagsCompatFlag("helpxml", helpxml),
+ };
+ for (const auto& help_flag : help_flags) {
+ if (!help_flag.Parse(args)) {
+ LOG(ERROR) << "Failed to process help flag.";
+ return 1;
+ }
+ }
+
+ fruit::Injector<> injector(FlagsComponent);
+ auto flag_features = injector.getMultibindings<FlagFeature>();
+ CF_EXPECT(FlagFeature::ProcessFlags(flag_features, args),
+ "Failed to parse flags.");
+
+ if (help || help_str != "") {
+ LOG(WARNING) << "TODO(schuffelen): Implement `--help` for assemble_cvd.";
+ LOG(WARNING) << "In the meantime, call `launch_cvd --help`";
+ return 1;
+ } else if (helpxml) {
+ if (!FlagFeature::WriteGflagsHelpXml(flag_features, std::cout)) {
+ LOG(ERROR) << "Failure in writing gflags helpxml output";
+ }
+ std::exit(1); // For parity with gflags
+ }
+ // TODO(schuffelen): Put in "unknown flag" guards after gflags is removed.
+ // gflags either consumes all arguments that start with - or leaves all of
+ // them in place, and either errors out on unknown flags or accepts any flags.
+
+ auto kernel_config =
+ CF_EXPECT(GetKernelConfigAndSetDefaults(), "Failed to parse arguments");
auto config =
- InitFilesystemAndCreateConfig(std::move(fetcher_config), kernel_config);
+ CF_EXPECT(InitFilesystemAndCreateConfig(std::move(fetcher_config),
+ kernel_config, injector),
+ "Failed to create config");
std::cout << GetConfigFilePath(*config) << "\n";
std::cout << std::flush;
@@ -261,5 +383,7 @@
} // namespace cuttlefish
int main(int argc, char** argv) {
- return cuttlefish::AssembleCvdMain(argc, argv);
+ auto res = cuttlefish::AssembleCvdMain(argc, argv);
+ CHECK(res.ok()) << "assemble_cvd failed: \n" << res.error();
+ return *res;
}
diff --git a/host/commands/assemble_cvd/boot_config.cc b/host/commands/assemble_cvd/boot_config.cc
index b433940..7c9f8b1 100644
--- a/host/commands/assemble_cvd/boot_config.cc
+++ b/host/commands/assemble_cvd/boot_config.cc
@@ -28,7 +28,9 @@
#include "common/libs/utils/environment.h"
#include "common/libs/utils/files.h"
+#include "common/libs/utils/size_utils.h"
#include "common/libs/utils/subprocess.h"
+#include "host/libs/config/bootconfig_args.h"
#include "host/libs/config/cuttlefish_config.h"
#include "host/libs/config/kernel_args.h"
#include "host/libs/vm_manager/crosvm_manager.h"
@@ -39,38 +41,38 @@
DECLARE_bool(pause_in_bootloader);
DECLARE_string(vm_manager);
+// Taken from external/avb/avbtool.py; this define is not in the headers
+#define MAX_AVB_METADATA_SIZE 69632ul
+
namespace cuttlefish {
namespace {
size_t WriteEnvironment(const CuttlefishConfig& config,
- const std::vector<std::string>& kernel_args,
+ const std::string& kernel_args,
const std::string& env_path) {
std::ostringstream env;
- env << "bootargs=" << android::base::Join(kernel_args, " ") << '\0';
- if (!config.boot_slot().empty()) {
- env << "android_slot_suffix=_" << config.boot_slot() << '\0';
- }
- if(FLAGS_pause_in_bootloader) {
- env << "bootdelay=-1" << '\0';
+ if (!kernel_args.empty()) {
+ env << "uenvcmd=setenv bootargs \"$cbootargs " << kernel_args << "\" && ";
} else {
- env << "bootdelay=0" << '\0';
+ env << "uenvcmd=setenv bootargs \"$cbootargs\" && ";
}
-
- // Note that the 0 index points to the GPT table.
- env << "bootcmd=boot_android virtio 0#misc" << '\0';
- if (FLAGS_vm_manager == CrosvmManager::name() &&
- config.target_arch() == Arch::Arm64) {
- env << "fdtaddr=0x80000000" << '\0';
+ if (FLAGS_pause_in_bootloader) {
+ env << "if test $paused -ne 1; then paused=1; else run bootcmd_android; fi";
} else {
- env << "fdtaddr=0x40000000" << '\0';
+ env << "run bootcmd_android";
}
env << '\0';
+ if (!config.boot_slot().empty()) {
+ env << "android_slot_suffix=_" << config.boot_slot() << '\0';
+ }
+ env << '\0';
+
std::string env_str = env.str();
std::ofstream file_out(env_path.c_str(), std::ios::binary);
file_out << env_str;
- if(!file_out.good()) {
+ if (!file_out.good()) {
return 0;
}
@@ -79,42 +81,113 @@
} // namespace
+class InitBootloaderEnvPartitionImpl : public InitBootloaderEnvPartition {
+ public:
+ INJECT(InitBootloaderEnvPartitionImpl(
+ const CuttlefishConfig& config,
+ const CuttlefishConfig::InstanceSpecific& instance))
+ : config_(config), instance_(instance) {}
-bool InitBootloaderEnvPartition(const CuttlefishConfig& config,
- const CuttlefishConfig::InstanceSpecific& instance) {
- auto boot_env_image_path = instance.uboot_env_image_path();
- auto tmp_boot_env_image_path = boot_env_image_path + ".tmp";
- auto uboot_env_path = instance.PerInstancePath("mkenvimg_input");
- auto kernel_args = KernelCommandLineFromConfig(config);
- if(!WriteEnvironment(config, kernel_args, uboot_env_path)) {
- LOG(ERROR) << "Unable to write out plaintext env '" << uboot_env_path << ".'";
- return false;
- }
+ // SetupFeature
+ std::string Name() const override { return "InitBootloaderEnvPartitionImpl"; }
+ bool Enabled() const override { return !config_.protected_vm(); }
- auto mkimage_path = HostBinaryPath("mkenvimage");
- Command cmd(mkimage_path);
- cmd.AddParameter("-s");
- cmd.AddParameter("4096");
- cmd.AddParameter("-o");
- cmd.AddParameter(tmp_boot_env_image_path);
- cmd.AddParameter(uboot_env_path);
- int success = cmd.Start().Wait();
- if (success != 0) {
- LOG(ERROR) << "Unable to run mkenvimage. Exited with status " << success;
- return false;
- }
-
- if(!FileExists(boot_env_image_path) || ReadFile(boot_env_image_path) != ReadFile(tmp_boot_env_image_path)) {
- if(!RenameFile(tmp_boot_env_image_path, boot_env_image_path)) {
- LOG(ERROR) << "Unable to delete the old env image.";
+ private:
+ std::unordered_set<SetupFeature*> Dependencies() const override { return {}; }
+ bool Setup() override {
+ auto boot_env_image_path = instance_.uboot_env_image_path();
+ auto tmp_boot_env_image_path = boot_env_image_path + ".tmp";
+ auto uboot_env_path = instance_.PerInstancePath("mkenvimg_input");
+ auto kernel_cmdline =
+ android::base::Join(KernelCommandLineFromConfig(config_), " ");
+ // If the bootconfig isn't supported in the guest kernel, the bootconfig
+ // args need to be passed in via the uboot env. This won't be an issue for
+ // protect kvm which is running a kernel with bootconfig support.
+ if (!config_.bootconfig_supported()) {
+ auto bootconfig_args = android::base::Join(
+ BootconfigArgsFromConfig(config_, instance_), " ");
+ // "androidboot.hardware" kernel parameter has changed to "hardware" in
+ // bootconfig and needs to be replaced before being used in the kernel
+ // cmdline.
+ bootconfig_args = android::base::StringReplace(
+ bootconfig_args, " hardware=", " androidboot.hardware=", true);
+ // TODO(b/182417593): Until we pass the module parameters through
+ // modules.options, we pass them through bootconfig using
+ // 'kernel.<key>=<value>' But if we don't support bootconfig, we need to
+ // rename them back to the old cmdline version
+ bootconfig_args =
+ android::base::StringReplace(bootconfig_args, " kernel.", " ", true);
+ kernel_cmdline += " ";
+ kernel_cmdline += bootconfig_args;
+ }
+ if (!WriteEnvironment(config_, kernel_cmdline, uboot_env_path)) {
+ LOG(ERROR) << "Unable to write out plaintext env '" << uboot_env_path
+ << ".'";
return false;
}
- LOG(DEBUG) << "Updated bootloader environment image.";
- } else {
- RemoveFile(tmp_boot_env_image_path);
+
+ auto mkimage_path = HostBinaryPath("mkenvimage_slim");
+ Command cmd(mkimage_path);
+ cmd.AddParameter("-output_path");
+ cmd.AddParameter(tmp_boot_env_image_path);
+ cmd.AddParameter("-input_path");
+ cmd.AddParameter(uboot_env_path);
+ int success = cmd.Start().Wait();
+ if (success != 0) {
+ LOG(ERROR) << "Unable to run mkenvimage_slim. Exited with status "
+ << success;
+ return false;
+ }
+
+ const off_t boot_env_size_bytes = AlignToPowerOf2(
+ MAX_AVB_METADATA_SIZE + 4096, PARTITION_SIZE_SHIFT);
+
+ auto avbtool_path = HostBinaryPath("avbtool");
+ Command boot_env_hash_footer_cmd(avbtool_path);
+ boot_env_hash_footer_cmd.AddParameter("add_hash_footer");
+ boot_env_hash_footer_cmd.AddParameter("--image");
+ boot_env_hash_footer_cmd.AddParameter(tmp_boot_env_image_path);
+ boot_env_hash_footer_cmd.AddParameter("--partition_size");
+ boot_env_hash_footer_cmd.AddParameter(boot_env_size_bytes);
+ boot_env_hash_footer_cmd.AddParameter("--partition_name");
+ boot_env_hash_footer_cmd.AddParameter("uboot_env");
+ boot_env_hash_footer_cmd.AddParameter("--key");
+ boot_env_hash_footer_cmd.AddParameter(
+ DefaultHostArtifactsPath("etc/cvd_avb_testkey.pem"));
+ boot_env_hash_footer_cmd.AddParameter("--algorithm");
+ boot_env_hash_footer_cmd.AddParameter("SHA256_RSA4096");
+ success = boot_env_hash_footer_cmd.Start().Wait();
+ if (success != 0) {
+ LOG(ERROR) << "Unable to run append hash footer. Exited with status "
+ << success;
+ return false;
+ }
+
+ if (!FileExists(boot_env_image_path) ||
+ ReadFile(boot_env_image_path) != ReadFile(tmp_boot_env_image_path)) {
+ if (!RenameFile(tmp_boot_env_image_path, boot_env_image_path)) {
+ LOG(ERROR) << "Unable to delete the old env image.";
+ return false;
+ }
+ LOG(DEBUG) << "Updated bootloader environment image.";
+ } else {
+ RemoveFile(tmp_boot_env_image_path);
+ }
+
+ return true;
}
- return true;
+ const CuttlefishConfig& config_;
+ const CuttlefishConfig::InstanceSpecific& instance_;
+};
+
+fruit::Component<fruit::Required<const CuttlefishConfig,
+ const CuttlefishConfig::InstanceSpecific>,
+ InitBootloaderEnvPartition>
+InitBootloaderEnvPartitionComponent() {
+ return fruit::createComponent()
+ .bind<InitBootloaderEnvPartition, InitBootloaderEnvPartitionImpl>()
+ .addMultibinding<SetupFeature, InitBootloaderEnvPartition>();
}
} // namespace cuttlefish
diff --git a/host/commands/assemble_cvd/boot_config.h b/host/commands/assemble_cvd/boot_config.h
index 1a81b79..d89c922 100644
--- a/host/commands/assemble_cvd/boot_config.h
+++ b/host/commands/assemble_cvd/boot_config.h
@@ -16,11 +16,19 @@
#pragma once
#include <string>
-#include <host/libs/config/cuttlefish_config.h>
+
+#include <fruit/fruit.h>
+
+#include "host/libs/config/cuttlefish_config.h"
+#include "host/libs/config/feature.h"
namespace cuttlefish {
-bool InitBootloaderEnvPartition(const CuttlefishConfig& config,
- const CuttlefishConfig::InstanceSpecific& instance);
+class InitBootloaderEnvPartition : public SetupFeature {};
+
+fruit::Component<fruit::Required<const CuttlefishConfig,
+ const CuttlefishConfig::InstanceSpecific>,
+ InitBootloaderEnvPartition>
+InitBootloaderEnvPartitionComponent();
} // namespace cuttlefish
diff --git a/host/commands/assemble_cvd/boot_image_utils.cc b/host/commands/assemble_cvd/boot_image_utils.cc
index cfad23f..ec9e93f 100644
--- a/host/commands/assemble_cvd/boot_image_utils.cc
+++ b/host/commands/assemble_cvd/boot_image_utils.cc
@@ -71,32 +71,6 @@
return true;
}
-bool UnpackBootImage(const std::string& boot_image_path,
- const std::string& unpack_dir) {
- auto unpack_path = HostBinaryPath("unpack_bootimg");
- Command unpack_cmd(unpack_path);
- unpack_cmd.AddParameter("--boot_img");
- unpack_cmd.AddParameter(boot_image_path);
- unpack_cmd.AddParameter("--out");
- unpack_cmd.AddParameter(unpack_dir);
-
- auto output_file = SharedFD::Creat(unpack_dir + "/boot_params", 0666);
- if (!output_file->IsOpen()) {
- LOG(ERROR) << "Unable to create intermediate boot params file: "
- << output_file->StrError();
- return false;
- }
- unpack_cmd.RedirectStdIO(Subprocess::StdIOChannel::kStdOut, output_file);
-
- int success = unpack_cmd.Start().Wait();
- if (success != 0) {
- LOG(ERROR) << "Unable to run unpack_bootimg. Exited with status "
- << success;
- return false;
- }
- return true;
-}
-
void RepackVendorRamdisk(const std::string& kernel_modules_ramdisk_path,
const std::string& original_ramdisk_path,
const std::string& new_ramdisk_path,
@@ -141,6 +115,34 @@
final_rd << ramdisk_a.rdbuf() << ramdisk_b.rdbuf();
}
+} // namespace
+
+bool UnpackBootImage(const std::string& boot_image_path,
+ const std::string& unpack_dir) {
+ auto unpack_path = HostBinaryPath("unpack_bootimg");
+ Command unpack_cmd(unpack_path);
+ unpack_cmd.AddParameter("--boot_img");
+ unpack_cmd.AddParameter(boot_image_path);
+ unpack_cmd.AddParameter("--out");
+ unpack_cmd.AddParameter(unpack_dir);
+
+ auto output_file = SharedFD::Creat(unpack_dir + "/boot_params", 0666);
+ if (!output_file->IsOpen()) {
+ LOG(ERROR) << "Unable to create intermediate boot params file: "
+ << output_file->StrError();
+ return false;
+ }
+ unpack_cmd.RedirectStdIO(Subprocess::StdIOChannel::kStdOut, output_file);
+
+ int success = unpack_cmd.Start().Wait();
+ if (success != 0) {
+ LOG(ERROR) << "Unable to run unpack_bootimg. Exited with status "
+ << success;
+ return false;
+ }
+ return true;
+}
+
bool UnpackVendorBootImageIfNotUnpacked(
const std::string& vendor_boot_image_path, const std::string& unpack_dir) {
// the vendor boot params file is created during the first unpack. If it's
@@ -189,8 +191,6 @@
return true;
}
-} // namespace
-
bool RepackBootImage(const std::string& new_kernel_path,
const std::string& boot_image_path,
const std::string& new_boot_image_path,
@@ -222,11 +222,20 @@
return false;
}
- auto fd = SharedFD::Open(tmp_boot_image_path, O_RDWR);
- auto original_size = FileSize(boot_image_path);
- CHECK(fd->Truncate(original_size) == 0)
- << "`truncate --size=" << original_size << " " << tmp_boot_image_path << "` "
- << "failed: " << fd->StrError();
+ auto avbtool_path = HostBinaryPath("avbtool");
+ Command avb_cmd(avbtool_path);
+ avb_cmd.AddParameter("add_hash_footer");
+ avb_cmd.AddParameter("--image");
+ avb_cmd.AddParameter(tmp_boot_image_path);
+ avb_cmd.AddParameter("--partition_size");
+ avb_cmd.AddParameter(FileSize(boot_image_path));
+ avb_cmd.AddParameter("--partition_name");
+ avb_cmd.AddParameter("boot");
+ success = avb_cmd.Start().Wait();
+ if (success != 0) {
+ LOG(ERROR) << "Unable to run avbtool. Exited with status " << success;
+ return false;
+ }
return DeleteTmpFileIfNotChanged(tmp_boot_image_path, new_boot_image_path);
}
@@ -235,8 +244,6 @@
const std::string& vendor_boot_image_path,
const std::string& new_vendor_boot_image_path,
const std::string& unpack_dir,
- const std::string& repack_dir,
- const std::vector<std::string>& bootconfig_args,
bool bootconfig_supported) {
if (UnpackVendorBootImageIfNotUnpacked(vendor_boot_image_path, unpack_dir) ==
false) {
@@ -255,23 +262,15 @@
ramdisk_path = unpack_dir + "/" + CONCATENATED_VENDOR_RAMDISK;
}
- auto bootconfig_fd = SharedFD::Creat(repack_dir + "/bootconfig", 0666);
- if (!bootconfig_fd->IsOpen()) {
- LOG(ERROR) << "Unable to create intermediate bootconfig file: "
- << bootconfig_fd->StrError();
- return false;
- }
std::string bootconfig = ReadFile(unpack_dir + "/bootconfig");
- bootconfig_fd->Write(bootconfig.c_str(), bootconfig.size());
LOG(DEBUG) << "Bootconfig parameters from vendor boot image are "
- << ReadFile(repack_dir + "/bootconfig");
+ << bootconfig;
std::string vendor_boot_params = ReadFile(unpack_dir + "/vendor_boot_params");
auto kernel_cmdline =
ExtractValue(vendor_boot_params, "vendor command line args: ") +
(bootconfig_supported
? ""
- : " " + android::base::StringReplace(bootconfig, "\n", " ", true) +
- " " + android::base::Join(bootconfig_args, " "));
+ : " " + android::base::StringReplace(bootconfig, "\n", " ", true));
if (!bootconfig_supported) {
// TODO(b/182417593): Until we pass the module parameters through
// modules.options, we pass them through bootconfig using
@@ -280,8 +279,7 @@
kernel_cmdline = android::base::StringReplace(
kernel_cmdline, " kernel.", " ", true);
}
- LOG(DEBUG) << "Cmdline from vendor boot image and config is "
- << kernel_cmdline;
+ LOG(DEBUG) << "Cmdline from vendor boot image is " << kernel_cmdline;
auto tmp_vendor_boot_image_path = new_vendor_boot_image_path + TMP_EXTENSION;
auto repack_path = HostBinaryPath("mkbootimg");
@@ -298,7 +296,7 @@
repack_cmd.AddParameter(unpack_dir + "/dtb");
if (bootconfig_supported) {
repack_cmd.AddParameter("--vendor_bootconfig");
- repack_cmd.AddParameter(repack_dir + "/bootconfig");
+ repack_cmd.AddParameter(unpack_dir + "/bootconfig");
}
int success = repack_cmd.Start().Wait();
@@ -307,11 +305,20 @@
return false;
}
- auto fd = SharedFD::Open(tmp_vendor_boot_image_path, O_RDWR);
- auto original_size = FileSize(vendor_boot_image_path);
- CHECK(fd->Truncate(original_size) == 0)
- << "`truncate --size=" << original_size << " " << tmp_vendor_boot_image_path << "` "
- << "failed: " << fd->StrError();
+ auto avbtool_path = HostBinaryPath("avbtool");
+ Command avb_cmd(avbtool_path);
+ avb_cmd.AddParameter("add_hash_footer");
+ avb_cmd.AddParameter("--image");
+ avb_cmd.AddParameter(tmp_vendor_boot_image_path);
+ avb_cmd.AddParameter("--partition_size");
+ avb_cmd.AddParameter(FileSize(vendor_boot_image_path));
+ avb_cmd.AddParameter("--partition_name");
+ avb_cmd.AddParameter("vendor_boot");
+ success = avb_cmd.Start().Wait();
+ if (success != 0) {
+ LOG(ERROR) << "Unable to run avbtool. Exited with status " << success;
+ return false;
+ }
return DeleteTmpFileIfNotChanged(tmp_vendor_boot_image_path, new_vendor_boot_image_path);
}
@@ -319,14 +326,75 @@
bool RepackVendorBootImageWithEmptyRamdisk(
const std::string& vendor_boot_image_path,
const std::string& new_vendor_boot_image_path,
- const std::string& unpack_dir, const std::string& repack_dir,
- const std::vector<std::string>& bootconfig_args,
- bool bootconfig_supported) {
+ const std::string& unpack_dir, bool bootconfig_supported) {
auto empty_ramdisk_file =
SharedFD::Creat(unpack_dir + "/empty_ramdisk", 0666);
return RepackVendorBootImage(
unpack_dir + "/empty_ramdisk", vendor_boot_image_path,
- new_vendor_boot_image_path, unpack_dir, repack_dir, bootconfig_args,
- bootconfig_supported);
+ new_vendor_boot_image_path, unpack_dir, bootconfig_supported);
+}
+
+void RepackGem5BootImage(const std::string& initrd_path,
+ const std::string& bootconfig_path,
+ const std::string& unpack_dir) {
+ // Simulate per-instance what the bootloader would usually do
+ // Since on other devices this runs every time, just do it here every time
+ std::ofstream final_rd(initrd_path,
+ std::ios_base::binary | std::ios_base::trunc);
+
+ std::ifstream boot_ramdisk(unpack_dir + "/ramdisk",
+ std::ios_base::binary);
+ std::ifstream vendor_boot_ramdisk(unpack_dir +
+ "/concatenated_vendor_ramdisk",
+ std::ios_base::binary);
+
+ std::ifstream vendor_boot_bootconfig(unpack_dir + "/bootconfig",
+ std::ios_base::binary |
+ std::ios_base::ate);
+
+ auto vb_size = vendor_boot_bootconfig.tellg();
+ vendor_boot_bootconfig.seekg(0);
+
+ std::ifstream persistent_bootconfig(bootconfig_path,
+ std::ios_base::binary |
+ std::ios_base::ate);
+
+ auto pb_size = persistent_bootconfig.tellg();
+ persistent_bootconfig.seekg(0);
+
+ // Build the bootconfig string, trim it, and write the length, checksum
+ // and trailer bytes
+
+ std::string bootconfig =
+ "androidboot.slot_suffix=_a\n"
+ "androidboot.force_normal_boot=1\n"
+ "androidboot.verifiedbootstate=orange\n";
+ auto bootconfig_size = bootconfig.size();
+ bootconfig.resize(bootconfig_size + (uint64_t)(vb_size + pb_size), '\0');
+ vendor_boot_bootconfig.read(&bootconfig[bootconfig_size], vb_size);
+ persistent_bootconfig.read(&bootconfig[bootconfig_size + vb_size], pb_size);
+ // Trim the block size padding from the persistent bootconfig
+ bootconfig.erase(bootconfig.find_last_not_of('\0'));
+
+ // Write out the ramdisks and bootconfig blocks
+ final_rd << boot_ramdisk.rdbuf() << vendor_boot_ramdisk.rdbuf()
+ << bootconfig;
+
+ // Append bootconfig length
+ bootconfig_size = bootconfig.size();
+ final_rd.write(reinterpret_cast<const char *>(&bootconfig_size),
+ sizeof(uint32_t));
+
+ // Append bootconfig checksum
+ uint32_t bootconfig_csum = 0;
+ for (auto i = 0; i < bootconfig_size; i++) {
+ bootconfig_csum += bootconfig[i];
+ }
+ final_rd.write(reinterpret_cast<const char *>(&bootconfig_csum),
+ sizeof(uint32_t));
+
+ // Append bootconfig trailer
+ final_rd << "#BOOTCONFIG\n";
+ final_rd.close();
}
} // namespace cuttlefish
diff --git a/host/commands/assemble_cvd/boot_image_utils.h b/host/commands/assemble_cvd/boot_image_utils.h
index fafb514..c5a2d1b 100644
--- a/host/commands/assemble_cvd/boot_image_utils.h
+++ b/host/commands/assemble_cvd/boot_image_utils.h
@@ -27,12 +27,16 @@
const std::string& vendor_boot_image_path,
const std::string& new_vendor_boot_image_path,
const std::string& unpack_dir,
- const std::string& repack_dir,
- const std::vector<std::string>& bootconfig_args,
bool bootconfig_supported);
bool RepackVendorBootImageWithEmptyRamdisk(
const std::string& vendor_boot_image_path,
const std::string& new_vendor_boot_image_path,
- const std::string& unpack_dir, const std::string& repack_dir,
- const std::vector<std::string>& bootconfig_args, bool bootconfig_supported);
+ const std::string& unpack_dir, bool bootconfig_supported);
+bool UnpackBootImage(const std::string& boot_image_path,
+ const std::string& unpack_dir);
+bool UnpackVendorBootImageIfNotUnpacked(
+ const std::string& vendor_boot_image_path, const std::string& unpack_dir);
+void RepackGem5BootImage(const std::string& initrd_path,
+ const std::string& bootconfig_path,
+ const std::string& unpack_dir);
}
diff --git a/host/commands/assemble_cvd/clean.cc b/host/commands/assemble_cvd/clean.cc
index 6197a8f..3a9637d 100644
--- a/host/commands/assemble_cvd/clean.cc
+++ b/host/commands/assemble_cvd/clean.cc
@@ -23,42 +23,40 @@
#include <vector>
#include <android-base/logging.h>
+#include <android-base/strings.h>
-#include "host/commands/assemble_cvd/flags.h"
#include "common/libs/utils/files.h"
+#include "common/libs/utils/result.h"
+#include "host/commands/assemble_cvd/flags.h"
namespace cuttlefish {
namespace {
-bool CleanPriorFiles(const std::string& path, const std::set<std::string>& preserving) {
+Result<void> CleanPriorFiles(const std::string& path,
+ const std::set<std::string>& preserving) {
if (preserving.count(cpp_basename(path))) {
LOG(DEBUG) << "Preserving: " << path;
- return true;
+ return {};
}
struct stat statbuf;
if (lstat(path.c_str(), &statbuf) < 0) {
int error_num = errno;
if (error_num == ENOENT) {
- return true;
+ return {};
} else {
- LOG(ERROR) << "Could not stat \"" << path << "\": " << strerror(error_num);
- return false;
+ return CF_ERRNO("Could not stat \"" << path);
}
}
if ((statbuf.st_mode & S_IFMT) != S_IFDIR) {
LOG(DEBUG) << "Deleting: " << path;
if (unlink(path.c_str()) < 0) {
- int error_num = errno;
- LOG(ERROR) << "Could not unlink \"" << path << "\", error was " << strerror(error_num);
- return false;
+ return CF_ERRNO("Could not unlink \"" << path << "\"");
}
- return true;
+ return {};
}
std::unique_ptr<DIR, int(*)(DIR*)> dir(opendir(path.c_str()), closedir);
if (!dir) {
- int error_num = errno;
- LOG(ERROR) << "Could not clean \"" << path << "\": error was " << strerror(error_num);
- return false;
+ return CF_ERRNO("Could not clean \"" << path << "\"");
}
for (auto entity = readdir(dir.get()); entity != nullptr; entity = readdir(dir.get())) {
std::string entity_name(entity->d_name);
@@ -66,30 +64,28 @@
continue;
}
std::string entity_path = path + "/" + entity_name;
- if (!CleanPriorFiles(entity_path.c_str(), preserving)) {
- return false;
- }
+ CF_EXPECT(CleanPriorFiles(entity_path.c_str(), preserving),
+ "CleanPriorFiles for \""
+ << path << "\" failed on recursing into \"" << entity_path
+ << "\"");
}
if (rmdir(path.c_str()) < 0) {
if (!(errno == EEXIST || errno == ENOTEMPTY)) {
// If EEXIST or ENOTEMPTY, probably because a file was preserved
- int error_num = errno;
- LOG(ERROR) << "Could not rmdir \"" << path << "\", error was " << strerror(error_num);
- return false;
+ return CF_ERRNO("Could not rmdir \"" << path << "\"");
}
}
- return true;
+ return {};
}
-bool CleanPriorFiles(const std::vector<std::string>& paths, const std::set<std::string>& preserving) {
+Result<void> CleanPriorFiles(const std::vector<std::string>& paths,
+ const std::set<std::string>& preserving) {
std::string prior_files;
for (auto path : paths) {
struct stat statbuf;
if (stat(path.c_str(), &statbuf) < 0 && errno != ENOENT) {
// If ENOENT, it doesn't exist yet, so there is no work to do'
- int error_num = errno;
- LOG(ERROR) << "Could not stat \"" << path << "\": " << strerror(error_num);
- return false;
+ return CF_ERRNO("Could not stat \"" << path << "\"");
}
bool is_directory = (statbuf.st_mode & S_IFMT) == S_IFDIR;
prior_files += (is_directory ? (path + "/*") : path) + " ";
@@ -98,25 +94,19 @@
std::string lsof_cmd = "lsof -t " + prior_files + " >/dev/null 2>&1";
int rval = std::system(lsof_cmd.c_str());
// lsof returns 0 if any of the files are open
- if (WEXITSTATUS(rval) == 0) {
- LOG(ERROR) << "Clean aborted: files are in use";
- return false;
- }
+ CF_EXPECT(WEXITSTATUS(rval) != 0, "Clean aborted: files are in use");
for (const auto& path : paths) {
- if (!CleanPriorFiles(path, preserving)) {
- LOG(ERROR) << "Remove of file under \"" << path << "\" failed";
- return false;
- }
+ CF_EXPECT(CleanPriorFiles(path, preserving),
+ "CleanPriorFiles failed for \"" << path << "\"");
}
- return true;
+ return {};
}
} // namespace
-bool CleanPriorFiles(
- const std::set<std::string>& preserving,
- const std::string& assembly_dir,
- const std::string& instance_dir) {
+Result<void> CleanPriorFiles(const std::set<std::string>& preserving,
+ const std::string& assembly_dir,
+ const std::vector<std::string>& instance_dirs) {
std::vector<std::string> paths = {
// Everything in the assembly directory
assembly_dir,
@@ -125,32 +115,13 @@
// The global link to the config file
GetGlobalConfigFileLink(),
};
-
- std::string runtime_dir_parent = cpp_dirname(AbsolutePath(instance_dir));
- std::string runtime_dirs_basename = cpp_basename(AbsolutePath(instance_dir));
-
- std::regex instance_dir_regex("^.+\\.[1-9]\\d*$");
- for (const auto& path : DirectoryContents(runtime_dir_parent)) {
- std::string absl_path = runtime_dir_parent + "/" + path;
- if((path.rfind(runtime_dirs_basename, 0) == 0) && std::regex_match(path, instance_dir_regex) &&
- DirectoryExists(absl_path)) {
- paths.push_back(absl_path);
- }
- }
- paths.push_back(instance_dir);
- return CleanPriorFiles(paths, preserving);
-}
-
-bool EnsureDirectoryExists(const std::string& directory_path) {
- if (!DirectoryExists(directory_path)) {
- LOG(DEBUG) << "Setting up " << directory_path;
- if (mkdir(directory_path.c_str(), S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH) < 0
- && errno != EEXIST) {
- PLOG(ERROR) << "Failed to create dir: \"" << directory_path << "\" ";
- return false;
- }
- }
- return true;
+ paths.insert(paths.end(), instance_dirs.begin(), instance_dirs.end());
+ using android::base::Join;
+ CF_EXPECT(CleanPriorFiles(paths, preserving),
+ "CleanPriorFiles("
+ << "paths = {" << Join(paths, ", ") << "}, "
+ << "preserving = {" << Join(preserving, ", ") << "}) failed");
+ return {};
}
} // namespace cuttlefish
diff --git a/host/commands/assemble_cvd/clean.h b/host/commands/assemble_cvd/clean.h
index 1f33685..3097279 100644
--- a/host/commands/assemble_cvd/clean.h
+++ b/host/commands/assemble_cvd/clean.h
@@ -18,13 +18,12 @@
#include <set>
#include <string>
+#include "common/libs/utils/result.h"
+
namespace cuttlefish {
-bool CleanPriorFiles(
- const std::set<std::string>& preserving,
- const std::string& assembly_dir,
- const std::string& instance_dir);
-
-bool EnsureDirectoryExists(const std::string& directory_path);
+Result<void> CleanPriorFiles(const std::set<std::string>& preserving,
+ const std::string& assembly_dir,
+ const std::vector<std::string>& instance_dirs);
} // namespace cuttlefish
diff --git a/host/commands/assemble_cvd/disk_builder.cpp b/host/commands/assemble_cvd/disk_builder.cpp
new file mode 100644
index 0000000..57ef039
--- /dev/null
+++ b/host/commands/assemble_cvd/disk_builder.cpp
@@ -0,0 +1,221 @@
+//
+// Copyright (C) 2022 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "host/commands/assemble_cvd/disk_builder.h"
+
+#include <fstream>
+#include <sstream>
+#include <string>
+#include <vector>
+
+#include <android-base/file.h>
+
+#include "common/libs/fs/shared_fd.h"
+#include "common/libs/utils/files.h"
+#include "host/libs/config/cuttlefish_config.h"
+#include "host/libs/image_aggregator/image_aggregator.h"
+#include "host/libs/vm_manager/crosvm_manager.h"
+
+namespace cuttlefish {
+
+static std::chrono::system_clock::time_point LastUpdatedInputDisk(
+ const std::vector<ImagePartition>& partitions) {
+ std::chrono::system_clock::time_point ret;
+ for (auto& partition : partitions) {
+ if (partition.label == "frp") {
+ continue;
+ }
+
+ auto partition_mod_time = FileModificationTime(partition.image_file_path);
+ if (partition_mod_time > ret) {
+ ret = partition_mod_time;
+ }
+ }
+ return ret;
+}
+
+DiskBuilder& DiskBuilder::Partitions(std::vector<ImagePartition> partitions) & {
+ partitions_ = std::move(partitions);
+ return *this;
+}
+DiskBuilder DiskBuilder::Partitions(std::vector<ImagePartition> partitions) && {
+ partitions_ = std::move(partitions);
+ return *this;
+}
+
+DiskBuilder& DiskBuilder::HeaderPath(std::string header_path) & {
+ header_path_ = std::move(header_path);
+ return *this;
+}
+DiskBuilder DiskBuilder::HeaderPath(std::string header_path) && {
+ header_path_ = std::move(header_path);
+ return *this;
+}
+
+DiskBuilder& DiskBuilder::FooterPath(std::string footer_path) & {
+ footer_path_ = std::move(footer_path);
+ return *this;
+}
+DiskBuilder DiskBuilder::FooterPath(std::string footer_path) && {
+ footer_path_ = std::move(footer_path);
+ return *this;
+}
+
+DiskBuilder& DiskBuilder::CrosvmPath(std::string crosvm_path) & {
+ crosvm_path_ = std::move(crosvm_path);
+ return *this;
+}
+DiskBuilder DiskBuilder::CrosvmPath(std::string crosvm_path) && {
+ crosvm_path_ = std::move(crosvm_path);
+ return *this;
+}
+
+DiskBuilder& DiskBuilder::VmManager(std::string vm_manager) & {
+ vm_manager_ = std::move(vm_manager);
+ return *this;
+}
+DiskBuilder DiskBuilder::VmManager(std::string vm_manager) && {
+ vm_manager_ = std::move(vm_manager);
+ return *this;
+}
+
+DiskBuilder& DiskBuilder::ConfigPath(std::string config_path) & {
+ config_path_ = std::move(config_path);
+ return *this;
+}
+DiskBuilder DiskBuilder::ConfigPath(std::string config_path) && {
+ config_path_ = std::move(config_path);
+ return *this;
+}
+
+DiskBuilder& DiskBuilder::CompositeDiskPath(std::string composite_disk_path) & {
+ composite_disk_path_ = std::move(composite_disk_path);
+ return *this;
+}
+DiskBuilder DiskBuilder::CompositeDiskPath(std::string composite_disk_path) && {
+ composite_disk_path_ = std::move(composite_disk_path);
+ return *this;
+}
+
+DiskBuilder& DiskBuilder::OverlayPath(std::string overlay_path) & {
+ overlay_path_ = std::move(overlay_path);
+ return *this;
+}
+DiskBuilder DiskBuilder::OverlayPath(std::string overlay_path) && {
+ overlay_path_ = std::move(overlay_path);
+ return *this;
+}
+
+DiskBuilder& DiskBuilder::ResumeIfPossible(bool resume_if_possible) & {
+ resume_if_possible_ = resume_if_possible;
+ return *this;
+}
+DiskBuilder DiskBuilder::ResumeIfPossible(bool resume_if_possible) && {
+ resume_if_possible_ = resume_if_possible;
+ return *this;
+}
+
+Result<std::string> DiskBuilder::TextConfig() {
+ std::ostringstream disk_conf;
+
+ CF_EXPECT(!vm_manager_.empty(), "Missing vm_manager");
+ disk_conf << vm_manager_ << "\n";
+
+ CF_EXPECT(!partitions_.empty(), "No partitions");
+ for (auto& partition : partitions_) {
+ disk_conf << partition.image_file_path << "\n";
+ }
+ return disk_conf.str();
+}
+
+Result<bool> DiskBuilder::WillRebuildCompositeDisk() {
+ if (!resume_if_possible_) {
+ return true;
+ }
+
+ CF_EXPECT(!config_path_.empty(), "No config path");
+ if (ReadFile(config_path_) != CF_EXPECT(TextConfig())) {
+ LOG(DEBUG) << "Composite disk text config mismatch";
+ return true;
+ }
+
+ CF_EXPECT(!partitions_.empty(), "No partitions");
+ auto last_component_mod_time = LastUpdatedInputDisk(partitions_);
+
+ CF_EXPECT(!composite_disk_path_.empty(), "No composite disk path");
+ auto composite_mod_time = FileModificationTime(composite_disk_path_);
+
+ if (composite_mod_time == decltype(composite_mod_time)()) {
+ LOG(DEBUG) << "No prior composite disk";
+ return true;
+ } else if (last_component_mod_time > composite_mod_time) {
+ LOG(DEBUG) << "Composite disk component file updated";
+ return true;
+ }
+
+ return false;
+}
+
+Result<bool> DiskBuilder::BuildCompositeDiskIfNecessary() {
+ if (!CF_EXPECT(WillRebuildCompositeDisk())) {
+ return false;
+ }
+
+ CF_EXPECT(!vm_manager_.empty());
+ if (vm_manager_ == vm_manager::CrosvmManager::name()) {
+ CF_EXPECT(!header_path_.empty(), "No header path");
+ CF_EXPECT(!footer_path_.empty(), "No footer path");
+ CreateCompositeDisk(partitions_, AbsolutePath(header_path_),
+ AbsolutePath(footer_path_),
+ AbsolutePath(composite_disk_path_));
+ } else {
+ // If this doesn't fit into the disk, it will fail while aggregating. The
+ // aggregator doesn't maintain any sparse attributes.
+ AggregateImage(partitions_, AbsolutePath(composite_disk_path_));
+ }
+
+ using android::base::WriteStringToFile;
+ CF_EXPECT(WriteStringToFile(CF_EXPECT(TextConfig()), config_path_), true);
+
+ return true;
+}
+
+Result<bool> DiskBuilder::BuildOverlayIfNecessary() {
+ bool can_reuse_overlay = resume_if_possible_;
+
+ CF_EXPECT(!overlay_path_.empty(), "Overlay path missing");
+ auto overlay_mod_time = FileModificationTime(overlay_path_);
+
+ CF_EXPECT(!composite_disk_path_.empty(), "Composite disk path missing");
+ auto composite_disk_mod_time = FileModificationTime(composite_disk_path_);
+ if (overlay_mod_time == decltype(overlay_mod_time)()) {
+ LOG(DEBUG) << "No prior overlay";
+ can_reuse_overlay = false;
+ } else if (overlay_mod_time < composite_disk_mod_time) {
+ LOG(DEBUG) << "Overlay is out of date";
+ can_reuse_overlay = false;
+ }
+
+ if (can_reuse_overlay) {
+ return false;
+ }
+
+ CF_EXPECT(!crosvm_path_.empty(), "crosvm binary missing");
+ CreateQcowOverlay(crosvm_path_, composite_disk_path_, overlay_path_);
+
+ return true;
+}
+
+} // namespace cuttlefish
diff --git a/host/commands/assemble_cvd/disk_builder.h b/host/commands/assemble_cvd/disk_builder.h
new file mode 100644
index 0000000..7a23a5a
--- /dev/null
+++ b/host/commands/assemble_cvd/disk_builder.h
@@ -0,0 +1,77 @@
+//
+// Copyright (C) 2022 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#pragma once
+
+#include <chrono>
+#include <string>
+#include <vector>
+
+#include "common/libs/utils/result.h"
+#include "host/libs/config/cuttlefish_config.h"
+#include "host/libs/image_aggregator/image_aggregator.h"
+
+namespace cuttlefish {
+
+class DiskBuilder {
+ public:
+ DiskBuilder& Partitions(std::vector<ImagePartition> partitions) &;
+ DiskBuilder Partitions(std::vector<ImagePartition> partitions) &&;
+
+ DiskBuilder& HeaderPath(std::string header_path) &;
+ DiskBuilder HeaderPath(std::string header_path) &&;
+
+ DiskBuilder& FooterPath(std::string footer_path) &;
+ DiskBuilder FooterPath(std::string footer_path) &&;
+
+ DiskBuilder& CrosvmPath(std::string crosvm_path) &;
+ DiskBuilder CrosvmPath(std::string crosvm_path) &&;
+
+ DiskBuilder& VmManager(std::string vm_manager) &;
+ DiskBuilder VmManager(std::string vm_manager) &&;
+
+ DiskBuilder& ConfigPath(std::string config_path) &;
+ DiskBuilder ConfigPath(std::string config_path) &&;
+
+ DiskBuilder& CompositeDiskPath(std::string composite_disk_path) &;
+ DiskBuilder CompositeDiskPath(std::string composite_disk_path) &&;
+
+ DiskBuilder& OverlayPath(std::string overlay_path) &;
+ DiskBuilder OverlayPath(std::string overlay_path) &&;
+
+ DiskBuilder& ResumeIfPossible(bool resume_if_possible) &;
+ DiskBuilder ResumeIfPossible(bool resume_if_possible) &&;
+
+ Result<bool> WillRebuildCompositeDisk();
+ /** Returns `true` if the file was actually rebuilt. */
+ Result<bool> BuildCompositeDiskIfNecessary();
+ /** Returns `true` if the file was actually rebuilt. */
+ Result<bool> BuildOverlayIfNecessary();
+
+ private:
+ Result<std::string> TextConfig();
+
+ std::vector<ImagePartition> partitions_;
+ std::string header_path_;
+ std::string footer_path_;
+ std::string vm_manager_;
+ std::string crosvm_path_;
+ std::string config_path_;
+ std::string composite_disk_path_;
+ std::string overlay_path_;
+ bool resume_if_possible_;
+};
+
+} // namespace cuttlefish
diff --git a/host/commands/assemble_cvd/disk_flags.cc b/host/commands/assemble_cvd/disk_flags.cc
index e5ccac9..c06d7d7 100644
--- a/host/commands/assemble_cvd/disk_flags.cc
+++ b/host/commands/assemble_cvd/disk_flags.cc
@@ -16,14 +16,14 @@
#include "host/commands/assemble_cvd/disk_flags.h"
+#include <android-base/logging.h>
+#include <android-base/strings.h>
+#include <fruit/fruit.h>
+#include <gflags/gflags.h>
#include <sys/statvfs.h>
#include <fstream>
-#include <android-base/logging.h>
-#include <android-base/strings.h>
-#include <gflags/gflags.h>
-
#include "common/libs/fs/shared_buf.h"
#include "common/libs/utils/environment.h"
#include "common/libs/utils/files.h"
@@ -31,22 +31,28 @@
#include "common/libs/utils/subprocess.h"
#include "host/commands/assemble_cvd/boot_config.h"
#include "host/commands/assemble_cvd/boot_image_utils.h"
+#include "host/commands/assemble_cvd/disk_builder.h"
#include "host/commands/assemble_cvd/super_image_mixer.h"
#include "host/libs/config/bootconfig_args.h"
#include "host/libs/config/cuttlefish_config.h"
#include "host/libs/config/data_image.h"
-#include "host/libs/image_aggregator/image_aggregator.h"
#include "host/libs/vm_manager/crosvm_manager.h"
+#include "host/libs/vm_manager/gem5_manager.h"
// Taken from external/avb/libavb/avb_slot_verify.c; this define is not in the headers
#define VBMETA_MAX_SIZE 65536ul
+// Taken from external/avb/avbtool.py; this define is not in the headers
+#define MAX_AVB_METADATA_SIZE 69632ul
-DEFINE_string(system_image_dir, cuttlefish::DefaultGuestImagePath(""),
- "Location of the system partition images.");
+DECLARE_string(system_image_dir);
DEFINE_string(boot_image, "",
"Location of cuttlefish boot image. If empty it is assumed to be "
"boot.img in the directory specified by -system_image_dir.");
+DEFINE_string(
+ init_boot_image, "",
+ "Location of cuttlefish init boot image. If empty it is assumed to "
+ "be init_boot.img in the directory specified by -system_image_dir.");
DEFINE_string(data_image, "", "Location of the data partition image.");
DEFINE_string(super_image, "", "Location of the super partition image.");
DEFINE_string(misc_image, "",
@@ -63,13 +69,23 @@
DEFINE_string(vbmeta_system_image, "",
"Location of cuttlefish vbmeta_system image. If empty it is assumed to "
"be vbmeta_system.img in the directory specified by -system_image_dir.");
-DEFINE_string(esp, "", "Path to ESP partition image (FAT formatted)");
+DEFINE_string(otheros_esp_image, "",
+ "Location of cuttlefish esp image. If the image does not exist, "
+ "and --otheros_root_image is specified, an esp partition image "
+ "is created with default bootloaders.");
+DEFINE_string(otheros_kernel_path, "",
+ "Location of cuttlefish otheros kernel.");
+DEFINE_string(otheros_initramfs_path, "",
+ "Location of cuttlefish otheros initramfs.img.");
+DEFINE_string(otheros_root_image, "",
+ "Location of cuttlefish otheros root filesystem image.");
DEFINE_int32(blank_metadata_image_mb, 16,
"The size of the blank metadata image to generate, MB.");
DEFINE_int32(blank_sdcard_image_mb, 2048,
"If enabled, the size of the blank sdcard image to generate, MB.");
+DECLARE_string(ap_rootfs_image);
DECLARE_string(bootloader);
DECLARE_bool(use_sdcard);
DECLARE_string(initramfs_path);
@@ -79,19 +95,22 @@
namespace cuttlefish {
-using vm_manager::CrosvmManager;
+using vm_manager::Gem5Manager;
-bool ResolveInstanceFiles() {
- if (FLAGS_system_image_dir.empty()) {
- LOG(ERROR) << "--system_image_dir must be specified.";
- return false;
- }
+Result<void> ResolveInstanceFiles() {
+ CF_EXPECT(!FLAGS_system_image_dir.empty(),
+ "--system_image_dir must be specified.");
// If user did not specify location of either of these files, expect them to
// be placed in --system_image_dir location.
std::string default_boot_image = FLAGS_system_image_dir + "/boot.img";
SetCommandLineOptionWithMode("boot_image", default_boot_image.c_str(),
google::FlagSettingMode::SET_FLAGS_DEFAULT);
+ std::string default_init_boot_image =
+ FLAGS_system_image_dir + "/init_boot.img";
+ SetCommandLineOptionWithMode("init_boot_image",
+ default_init_boot_image.c_str(),
+ google::FlagSettingMode::SET_FLAGS_DEFAULT);
std::string default_data_image = FLAGS_system_image_dir + "/userdata.img";
SetCommandLineOptionWithMode("data_image", default_data_image.c_str(),
google::FlagSettingMode::SET_FLAGS_DEFAULT);
@@ -104,6 +123,9 @@
std::string default_misc_image = FLAGS_system_image_dir + "/misc.img";
SetCommandLineOptionWithMode("misc_image", default_misc_image.c_str(),
google::FlagSettingMode::SET_FLAGS_DEFAULT);
+ std::string default_esp_image = FLAGS_system_image_dir + "/esp.img";
+ SetCommandLineOptionWithMode("otheros_esp_image", default_esp_image.c_str(),
+ google::FlagSettingMode::SET_FLAGS_DEFAULT);
std::string default_vendor_boot_image = FLAGS_system_image_dir
+ "/vendor_boot.img";
SetCommandLineOptionWithMode("vendor_boot_image",
@@ -118,84 +140,118 @@
default_vbmeta_system_image.c_str(),
google::FlagSettingMode::SET_FLAGS_DEFAULT);
- return true;
+ return {};
}
-std::vector<ImagePartition> os_composite_disk_config(
- const CuttlefishConfig::InstanceSpecific& instance) {
+std::vector<ImagePartition> GetOsCompositeDiskConfig() {
std::vector<ImagePartition> partitions;
- partitions.push_back(ImagePartition {
- .label = "misc",
- .image_file_path = FLAGS_misc_image,
+ partitions.push_back(ImagePartition{
+ .label = "misc",
+ .image_file_path = AbsolutePath(FLAGS_misc_image),
+ .read_only = true,
});
- if (!FLAGS_esp.empty()) {
- partitions.push_back(ImagePartition {
- .label = "esp",
- .image_file_path = FLAGS_esp,
- .type = kEfiSystemPartition,
+ partitions.push_back(ImagePartition{
+ .label = "boot_a",
+ .image_file_path = AbsolutePath(FLAGS_boot_image),
+ .read_only = true,
+ });
+ partitions.push_back(ImagePartition{
+ .label = "boot_b",
+ .image_file_path = AbsolutePath(FLAGS_boot_image),
+ .read_only = true,
+ });
+ partitions.push_back(ImagePartition{
+ .label = "init_boot_a",
+ .image_file_path = AbsolutePath(FLAGS_init_boot_image),
+ .read_only = true,
+ });
+ partitions.push_back(ImagePartition{
+ .label = "init_boot_b",
+ .image_file_path = AbsolutePath(FLAGS_init_boot_image),
+ .read_only = true,
+ });
+ partitions.push_back(ImagePartition{
+ .label = "vendor_boot_a",
+ .image_file_path = AbsolutePath(FLAGS_vendor_boot_image),
+ .read_only = true,
+ });
+ partitions.push_back(ImagePartition{
+ .label = "vendor_boot_b",
+ .image_file_path = AbsolutePath(FLAGS_vendor_boot_image),
+ .read_only = true,
+ });
+ partitions.push_back(ImagePartition{
+ .label = "vbmeta_a",
+ .image_file_path = AbsolutePath(FLAGS_vbmeta_image),
+ .read_only = true,
+ });
+ partitions.push_back(ImagePartition{
+ .label = "vbmeta_b",
+ .image_file_path = AbsolutePath(FLAGS_vbmeta_image),
+ .read_only = true,
+ });
+ partitions.push_back(ImagePartition{
+ .label = "vbmeta_system_a",
+ .image_file_path = AbsolutePath(FLAGS_vbmeta_system_image),
+ .read_only = true,
+ });
+ partitions.push_back(ImagePartition{
+ .label = "vbmeta_system_b",
+ .image_file_path = AbsolutePath(FLAGS_vbmeta_system_image),
+ .read_only = true,
+ });
+ partitions.push_back(ImagePartition{
+ .label = "super",
+ .image_file_path = AbsolutePath(FLAGS_super_image),
+ .read_only = true,
+ });
+ partitions.push_back(ImagePartition{
+ .label = "userdata",
+ .image_file_path = AbsolutePath(FLAGS_data_image),
+ .read_only = true,
+ });
+ partitions.push_back(ImagePartition{
+ .label = "metadata",
+ .image_file_path = AbsolutePath(FLAGS_metadata_image),
+ .read_only = true,
+ });
+ if (!FLAGS_otheros_root_image.empty()) {
+ partitions.push_back(ImagePartition{
+ .label = "otheros_esp",
+ .image_file_path = AbsolutePath(FLAGS_otheros_esp_image),
+ .type = kEfiSystemPartition,
+ .read_only = true,
+ });
+ partitions.push_back(ImagePartition{
+ .label = "otheros_root",
+ .image_file_path = AbsolutePath(FLAGS_otheros_root_image),
+ .read_only = true,
});
}
- partitions.push_back(ImagePartition {
- .label = "boot_a",
- .image_file_path = FLAGS_boot_image,
- });
- partitions.push_back(ImagePartition {
- .label = "boot_b",
- .image_file_path = FLAGS_boot_image,
- });
- // Boot image repacking is not supported on protected VMs. Repacking requires
- // resigning the image and keys on android hosts aren't trusted.
- if (!FLAGS_protected_vm) {
+ if (!FLAGS_ap_rootfs_image.empty()) {
partitions.push_back(ImagePartition{
- .label = "vendor_boot_a",
- .image_file_path = instance.vendor_boot_image_path(),
- });
- partitions.push_back(ImagePartition{
- .label = "vendor_boot_b",
- .image_file_path = instance.vendor_boot_image_path(),
- });
- } else {
- partitions.push_back(ImagePartition{
- .label = "vendor_boot_a",
- .image_file_path = FLAGS_vendor_boot_image,
- });
- partitions.push_back(ImagePartition{
- .label = "vendor_boot_b",
- .image_file_path = FLAGS_vendor_boot_image,
+ .label = "ap_rootfs",
+ .image_file_path = AbsolutePath(FLAGS_ap_rootfs_image),
+ .read_only = true,
});
}
- partitions.push_back(ImagePartition {
- .label = "vbmeta_a",
- .image_file_path = FLAGS_vbmeta_image,
- });
- partitions.push_back(ImagePartition {
- .label = "vbmeta_b",
- .image_file_path = FLAGS_vbmeta_image,
- });
- partitions.push_back(ImagePartition {
- .label = "vbmeta_system_a",
- .image_file_path = FLAGS_vbmeta_system_image,
- });
- partitions.push_back(ImagePartition {
- .label = "vbmeta_system_b",
- .image_file_path = FLAGS_vbmeta_system_image,
- });
- partitions.push_back(ImagePartition {
- .label = "super",
- .image_file_path = FLAGS_super_image,
- });
- partitions.push_back(ImagePartition {
- .label = "userdata",
- .image_file_path = FLAGS_data_image,
- });
- partitions.push_back(ImagePartition {
- .label = "metadata",
- .image_file_path = FLAGS_metadata_image,
- });
return partitions;
}
+DiskBuilder OsCompositeDiskBuilder(const CuttlefishConfig& config) {
+ return DiskBuilder()
+ .Partitions(GetOsCompositeDiskConfig())
+ .VmManager(config.vm_manager())
+ .CrosvmPath(config.crosvm_binary())
+ .ConfigPath(config.AssemblyPath("os_composite_disk_config.txt"))
+ .HeaderPath(config.AssemblyPath("os_composite_gpt_header.img"))
+ .FooterPath(config.AssemblyPath("os_composite_gpt_footer.img"))
+ .CompositeDiskPath(config.os_composite_disk_path())
+ .ResumeIfPossible(FLAGS_resume);
+}
+
std::vector<ImagePartition> persistent_composite_disk_config(
+ const CuttlefishConfig& config,
const CuttlefishConfig::InstanceSpecific& instance) {
std::vector<ImagePartition> partitions;
@@ -204,102 +260,28 @@
// cuttlefish.fragment in external/u-boot).
partitions.push_back(ImagePartition{
.label = "uboot_env",
- .image_file_path = instance.uboot_env_image_path(),
+ .image_file_path = AbsolutePath(instance.uboot_env_image_path()),
+ });
+ partitions.push_back(ImagePartition{
+ .label = "vbmeta",
+ .image_file_path = AbsolutePath(instance.vbmeta_path()),
});
if (!FLAGS_protected_vm) {
partitions.push_back(ImagePartition{
.label = "frp",
- .image_file_path = instance.factory_reset_protected_path(),
+ .image_file_path =
+ AbsolutePath(instance.factory_reset_protected_path()),
});
}
- partitions.push_back(ImagePartition{
- .label = "bootconfig",
- .image_file_path = instance.persistent_bootconfig_path(),
- });
+ if (config.bootconfig_supported()) {
+ partitions.push_back(ImagePartition{
+ .label = "bootconfig",
+ .image_file_path = AbsolutePath(instance.persistent_bootconfig_path()),
+ });
+ }
return partitions;
}
-static std::chrono::system_clock::time_point LastUpdatedInputDisk(
- const std::vector<ImagePartition>& partitions) {
- std::chrono::system_clock::time_point ret;
- for (auto& partition : partitions) {
- if (partition.label == "frp") {
- continue;
- }
-
- auto partition_mod_time = FileModificationTime(partition.image_file_path);
- if (partition_mod_time > ret) {
- ret = partition_mod_time;
- }
- }
- return ret;
-}
-
-bool ShouldCreateAllCompositeDisks(const CuttlefishConfig& config) {
- std::chrono::system_clock::time_point youngest_disk_img;
- for (auto& partition :
- os_composite_disk_config(config.ForDefaultInstance())) {
- auto partition_mod_time = FileModificationTime(partition.image_file_path);
- if (partition_mod_time > youngest_disk_img) {
- youngest_disk_img = partition_mod_time;
- }
- }
-
- // If the youngest partition img is younger than any composite disk, this fact implies that
- // the composite disks are all out of date and need to be reinitialized.
- for (auto& instance : config.Instances()) {
- if (!FileExists(instance.os_composite_disk_path())) {
- continue;
- }
- if (youngest_disk_img >
- FileModificationTime(instance.os_composite_disk_path())) {
- return true;
- }
- }
-
- return false;
-}
-
-bool DoesCompositeMatchCurrentDiskConfig(
- const std::string& prior_disk_config_path,
- const std::vector<ImagePartition>& partitions) {
- std::string current_disk_config_path = prior_disk_config_path + ".tmp";
- std::ostringstream disk_conf;
- for (auto& partition : partitions) {
- disk_conf << partition.image_file_path << "\n";
- }
-
- {
- // This file acts as a descriptor of the cuttlefish disk contents in a VMM agnostic way (VMMs
- // used are QEMU and CrosVM at the time of writing). This file is used to determine if the
- // disk config for the pending boot matches the disk from the past boot.
- std::ofstream file_out(current_disk_config_path.c_str(), std::ios::binary);
- file_out << disk_conf.str();
- CHECK(file_out.good()) << "Disk config verification failed.";
- }
-
- if (!FileExists(prior_disk_config_path) ||
- ReadFile(prior_disk_config_path) != ReadFile(current_disk_config_path)) {
- CHECK(cuttlefish::RenameFile(current_disk_config_path, prior_disk_config_path))
- << "Unable to delete the old disk config descriptor";
- LOG(DEBUG) << "Disk Config has changed since last boot. Regenerating composite disk.";
- return false;
- } else {
- RemoveFile(current_disk_config_path);
- return true;
- }
-}
-
-bool ShouldCreateCompositeDisk(const std::string& composite_disk_path,
- const std::vector<ImagePartition>& partitions) {
- if (!FileExists(composite_disk_path)) {
- return true;
- }
-
- auto composite_age = FileModificationTime(composite_disk_path);
- return composite_age < LastUpdatedInputDisk(partitions);
-}
-
static uint64_t AvailableSpaceAtPath(const std::string& path) {
struct statvfs vfs;
if (statvfs(path.c_str(), &vfs) != 0) {
@@ -312,285 +294,757 @@
return static_cast<uint64_t>(vfs.f_frsize) * vfs.f_bavail;
}
-bool CreateCompositeDisk(const CuttlefishConfig& config,
- const CuttlefishConfig::InstanceSpecific& instance) {
- if (!SharedFD::Open(instance.os_composite_disk_path().c_str(),
- O_WRONLY | O_CREAT, 0644)
- ->IsOpen()) {
- LOG(ERROR) << "Could not ensure " << instance.os_composite_disk_path()
- << " exists";
- return false;
+class BootImageRepacker : public SetupFeature {
+ public:
+ INJECT(BootImageRepacker(const CuttlefishConfig& config)) : config_(config) {}
+
+ // SetupFeature
+ std::string Name() const override { return "BootImageRepacker"; }
+ std::unordered_set<SetupFeature*> Dependencies() const override { return {}; }
+ bool Enabled() const override {
+ // If we are booting a protected VM, for now, assume that image repacking
+ // isn't trusted. Repacking requires resigning the image and keys from an
+ // android host aren't trusted.
+ return !config_.protected_vm();
}
- if (config.vm_manager() == CrosvmManager::name()) {
- // Check if filling in the sparse image would run out of disk space.
- auto existing_sizes = SparseFileSizes(FLAGS_data_image);
- if (existing_sizes.sparse_size == 0 && existing_sizes.disk_size == 0) {
- LOG(ERROR) << "Unable to determine size of \"" << FLAGS_data_image
- << "\". Does this file exist?";
- }
- auto available_space = AvailableSpaceAtPath(FLAGS_data_image);
- if (available_space < existing_sizes.sparse_size - existing_sizes.disk_size) {
- // TODO(schuffelen): Duplicate this check in run_cvd when it can run on a separate machine
- LOG(ERROR) << "Not enough space remaining in fs containing " << FLAGS_data_image;
- LOG(ERROR) << "Wanted " << (existing_sizes.sparse_size - existing_sizes.disk_size);
- LOG(ERROR) << "Got " << available_space;
+
+ protected:
+ bool Setup() override {
+ if (!FileHasContent(FLAGS_boot_image)) {
+ LOG(ERROR) << "File not found: " << FLAGS_boot_image;
return false;
- } else {
- LOG(DEBUG) << "Available space: " << available_space;
- LOG(DEBUG) << "Sparse size of \"" << FLAGS_data_image << "\": "
- << existing_sizes.sparse_size;
- LOG(DEBUG) << "Disk size of \"" << FLAGS_data_image << "\": "
- << existing_sizes.disk_size;
}
- std::string header_path =
- instance.PerInstancePath("os_composite_gpt_header.img");
- std::string footer_path =
- instance.PerInstancePath("os_composite_gpt_footer.img");
- CreateCompositeDisk(os_composite_disk_config(instance), header_path,
- footer_path, instance.os_composite_disk_path());
- } else {
- // If this doesn't fit into the disk, it will fail while aggregating. The
- // aggregator doesn't maintain any sparse attributes.
- AggregateImage(os_composite_disk_config(instance),
- instance.os_composite_disk_path());
- }
- return true;
-}
+ // The init_boot partition is be optional for testing boot.img
+ // with the ramdisk inside.
+ if (!FileHasContent(FLAGS_init_boot_image)) {
+ LOG(WARNING) << "File not found: " << FLAGS_init_boot_image;
+ }
-bool CreatePersistentCompositeDisk(
- const CuttlefishConfig& config,
- const CuttlefishConfig::InstanceSpecific& instance) {
- if (!SharedFD::Open(instance.persistent_composite_disk_path().c_str(),
- O_WRONLY | O_CREAT, 0644)
- ->IsOpen()) {
- LOG(ERROR) << "Could not ensure "
- << instance.persistent_composite_disk_path() << " exists";
- return false;
- }
- if (config.vm_manager() == CrosvmManager::name()) {
- std::string header_path =
- instance.PerInstancePath("persistent_composite_gpt_header.img");
- std::string footer_path =
- instance.PerInstancePath("persistent_composite_gpt_footer.img");
- CreateCompositeDisk(persistent_composite_disk_config(instance), header_path,
- footer_path, instance.persistent_composite_disk_path());
- } else {
- AggregateImage(persistent_composite_disk_config(instance),
- instance.persistent_composite_disk_path());
- }
- return true;
-}
+ if (!FileHasContent(FLAGS_vendor_boot_image)) {
+ LOG(ERROR) << "File not found: " << FLAGS_vendor_boot_image;
+ return false;
+ }
-static void RepackAllBootImages(const CuttlefishConfig* config) {
- CHECK(FileHasContent(FLAGS_boot_image))
- << "File not found: " << FLAGS_boot_image;
+ // Repacking a boot.img doesn't work with Gem5 because the user must always
+ // specify a vmlinux instead of an arm64 Image, and that file can be too
+ // large to be repacked. Skip repack of boot.img on Gem5, as we need to be
+ // able to extract the ramdisk.img in a later stage and so this step must
+ // not fail (..and the repacked kernel wouldn't be used anyway).
+ if (FLAGS_kernel_path.size() &&
+ config_.vm_manager() != Gem5Manager::name()) {
+ const std::string new_boot_image_path =
+ config_.AssemblyPath("boot_repacked.img");
+ bool success =
+ RepackBootImage(FLAGS_kernel_path, FLAGS_boot_image,
+ new_boot_image_path, config_.assembly_dir());
+ if (!success) {
+ LOG(ERROR) << "Failed to regenerate the boot image with the new kernel";
+ return false;
+ }
+ SetCommandLineOptionWithMode("boot_image", new_boot_image_path.c_str(),
+ google::FlagSettingMode::SET_FLAGS_DEFAULT);
+ }
- CHECK(FileHasContent(FLAGS_vendor_boot_image))
- << "File not found: " << FLAGS_vendor_boot_image;
-
- if (FLAGS_kernel_path.size()) {
- const std::string new_boot_image_path =
- config->AssemblyPath("boot_repacked.img");
- bool success = RepackBootImage(FLAGS_kernel_path, FLAGS_boot_image,
- new_boot_image_path, config->assembly_dir());
- CHECK(success) << "Failed to regenerate the boot image with the new kernel";
- SetCommandLineOptionWithMode("boot_image", new_boot_image_path.c_str(),
- google::FlagSettingMode::SET_FLAGS_DEFAULT);
- }
-
- for (auto instance : config->Instances()) {
- const std::string new_vendor_boot_image_path =
- instance.vendor_boot_image_path();
- const std::vector<std::string> boot_config_vector =
- BootconfigArgsFromConfig(*config, instance);
if (FLAGS_kernel_path.size() || FLAGS_initramfs_path.size()) {
+ const std::string new_vendor_boot_image_path =
+ config_.AssemblyPath("vendor_boot_repacked.img");
// Repack the vendor boot images if kernels and/or ramdisks are passed in.
if (FLAGS_initramfs_path.size()) {
bool success = RepackVendorBootImage(
FLAGS_initramfs_path, FLAGS_vendor_boot_image,
- new_vendor_boot_image_path, config->assembly_dir(),
- instance.instance_dir(), boot_config_vector,
- config->bootconfig_supported());
- CHECK(success) << "Failed to regenerate the vendor boot image with the "
- "new ramdisk";
- } else {
- // This control flow implies a kernel with all configs built in.
- // If it's just the kernel, repack the vendor boot image without a
- // ramdisk.
- bool success = RepackVendorBootImageWithEmptyRamdisk(
- FLAGS_vendor_boot_image, new_vendor_boot_image_path,
- config->assembly_dir(), instance.instance_dir(), boot_config_vector,
- config->bootconfig_supported());
- CHECK(success)
- << "Failed to regenerate the vendor boot image without a ramdisk";
+ new_vendor_boot_image_path, config_.assembly_dir(),
+ config_.bootconfig_supported());
+ if (!success) {
+ LOG(ERROR) << "Failed to regenerate the vendor boot image with the "
+ "new ramdisk";
+ } else {
+ // This control flow implies a kernel with all configs built in.
+ // If it's just the kernel, repack the vendor boot image without a
+ // ramdisk.
+ bool success = RepackVendorBootImageWithEmptyRamdisk(
+ FLAGS_vendor_boot_image, new_vendor_boot_image_path,
+ config_.assembly_dir(), config_.bootconfig_supported());
+ if (!success) {
+ LOG(ERROR) << "Failed to regenerate the vendor boot image without "
+ "a ramdisk";
+ return false;
+ }
+ }
+ SetCommandLineOptionWithMode(
+ "vendor_boot_image", new_vendor_boot_image_path.c_str(),
+ google::FlagSettingMode::SET_FLAGS_DEFAULT);
+ }
+ }
+ return true;
+ }
+
+ private:
+ const CuttlefishConfig& config_;
+};
+
+class Gem5ImageUnpacker : public SetupFeature {
+ public:
+ INJECT(Gem5ImageUnpacker(
+ const CuttlefishConfig& config,
+ BootImageRepacker& bir))
+ : config_(config),
+ bir_(bir) {}
+
+ // SetupFeature
+ std::string Name() const override { return "Gem5ImageUnpacker"; }
+
+ std::unordered_set<SetupFeature*> Dependencies() const override {
+ return {
+ static_cast<SetupFeature*>(&bir_),
+ };
+ }
+
+ bool Enabled() const override {
+ // Everything has a bootloader except gem5, so only run this for gem5
+ return config_.vm_manager() == Gem5Manager::name();
+ }
+
+ protected:
+ bool Setup() override {
+ /* Unpack the original or repacked boot and vendor boot ramdisks, so that
+ * we have access to the baked bootconfig and raw compressed ramdisks.
+ * This allows us to emulate what a bootloader would normally do, which
+ * Gem5 can't support itself. This code also copies the kernel again
+ * (because Gem5 only supports raw vmlinux) and handles the bootloader
+ * binaries specially. This code is just part of the solution; it only
+ * does the parts which are instance agnostic.
+ */
+
+ if (!FileHasContent(FLAGS_boot_image)) {
+ LOG(ERROR) << "File not found: " << FLAGS_boot_image;
+ return false;
+ }
+ // The init_boot partition is be optional for testing boot.img
+ // with the ramdisk inside.
+ if (!FileHasContent(FLAGS_init_boot_image)) {
+ LOG(WARNING) << "File not found: " << FLAGS_init_boot_image;
+ }
+
+ if (!FileHasContent(FLAGS_vendor_boot_image)) {
+ LOG(ERROR) << "File not found: " << FLAGS_vendor_boot_image;
+ return false;
+ }
+
+ const std::string unpack_dir = config_.assembly_dir();
+
+ bool success = UnpackBootImage(FLAGS_init_boot_image, unpack_dir);
+ if (!success) {
+ LOG(ERROR) << "Failed to extract the init boot image";
+ return false;
+ }
+
+ success = UnpackVendorBootImageIfNotUnpacked(FLAGS_vendor_boot_image,
+ unpack_dir);
+ if (!success) {
+ LOG(ERROR) << "Failed to extract the vendor boot image";
+ return false;
+ }
+
+ // Assume the user specified a kernel manually which is a vmlinux
+ std::ofstream kernel(unpack_dir + "/kernel", std::ios_base::binary |
+ std::ios_base::trunc);
+ std::ifstream vmlinux(FLAGS_kernel_path, std::ios_base::binary);
+ kernel << vmlinux.rdbuf();
+ kernel.close();
+
+ // Gem5 needs the bootloader binary to be a specific directory structure
+ // to find it. Create a 'binaries' directory and copy it into there
+ const std::string binaries_dir = unpack_dir + "/binaries";
+ if (mkdir(binaries_dir.c_str(), S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH) < 0
+ && errno != EEXIST) {
+ PLOG(ERROR) << "Failed to create dir: \"" << binaries_dir << "\" ";
+ return false;
+ }
+ std::ofstream bootloader(binaries_dir + "/" +
+ cpp_basename(FLAGS_bootloader),
+ std::ios_base::binary | std::ios_base::trunc);
+ std::ifstream src_bootloader(FLAGS_bootloader, std::ios_base::binary);
+ bootloader << src_bootloader.rdbuf();
+ bootloader.close();
+
+ // Gem5 also needs the ARM version of the bootloader, even though it
+ // doesn't use it. It'll even open it to check it's a valid ELF file.
+ // Work around this by copying such a named file from the same directory
+ std::ofstream boot_arm(binaries_dir + "/boot.arm",
+ std::ios_base::binary | std::ios_base::trunc);
+ std::ifstream src_boot_arm(cpp_dirname(FLAGS_bootloader) + "/boot.arm",
+ std::ios_base::binary);
+ boot_arm << src_boot_arm.rdbuf();
+ boot_arm.close();
+
+ return true;
+ }
+
+ private:
+ const CuttlefishConfig& config_;
+ BootImageRepacker& bir_;
+};
+
+class GeneratePersistentBootconfig : public SetupFeature {
+ public:
+ INJECT(GeneratePersistentBootconfig(
+ const CuttlefishConfig& config,
+ const CuttlefishConfig::InstanceSpecific& instance))
+ : config_(config), instance_(instance) {}
+
+ // SetupFeature
+ std::string Name() const override {
+ return "GeneratePersistentBootconfig";
+ }
+ bool Enabled() const override {
+ return (!config_.protected_vm());
+ }
+
+ private:
+ std::unordered_set<SetupFeature*> Dependencies() const override { return {}; }
+ bool Setup() override {
+ // Cuttlefish for the time being won't be able to support OTA from a
+ // non-bootconfig kernel to a bootconfig-kernel (or vice versa) IF the
+ // device is stopped (via stop_cvd). This is rarely an issue since OTA
+ // testing run on cuttlefish is done within one launch cycle of the device.
+ // If this ever becomes an issue, this code will have to be rewritten.
+ if(!config_.bootconfig_supported()) {
+ return true;
+ }
+
+ const auto bootconfig_path = instance_.persistent_bootconfig_path();
+ if (!FileExists(bootconfig_path)) {
+ if (!CreateBlankImage(bootconfig_path, 1 /* mb */, "none")) {
+ LOG(ERROR) << "Failed to create image at " << bootconfig_path;
+ return false;
+ }
+ }
+
+ auto bootconfig_fd = SharedFD::Open(bootconfig_path, O_RDWR);
+ if (!bootconfig_fd->IsOpen()) {
+ LOG(ERROR) << "Unable to open bootconfig file: "
+ << bootconfig_fd->StrError();
+ return false;
+ }
+
+ const std::string bootconfig =
+ android::base::Join(BootconfigArgsFromConfig(config_, instance_),
+ "\n") +
+ "\n";
+ ssize_t bytesWritten = WriteAll(bootconfig_fd, bootconfig);
+ LOG(DEBUG) << "bootconfig size is " << bytesWritten;
+ if (bytesWritten != bootconfig.size()) {
+ LOG(ERROR) << "Failed to write contents of bootconfig to \""
+ << bootconfig_path << "\"";
+ return false;
+ }
+ LOG(DEBUG) << "Bootconfig parameters from vendor boot image and config are "
+ << ReadFile(bootconfig_path);
+
+ if (bootconfig_fd->Truncate(bytesWritten) != 0) {
+ LOG(ERROR) << "`truncate --size=" << bytesWritten << " bytes "
+ << bootconfig_path << "` failed:" << bootconfig_fd->StrError();
+ return false;
+ }
+
+ if (config_.vm_manager() != Gem5Manager::name()) {
+ bootconfig_fd->Close();
+ const off_t bootconfig_size_bytes = AlignToPowerOf2(
+ MAX_AVB_METADATA_SIZE + bytesWritten, PARTITION_SIZE_SHIFT);
+
+ auto avbtool_path = HostBinaryPath("avbtool");
+ Command bootconfig_hash_footer_cmd(avbtool_path);
+ bootconfig_hash_footer_cmd.AddParameter("add_hash_footer");
+ bootconfig_hash_footer_cmd.AddParameter("--image");
+ bootconfig_hash_footer_cmd.AddParameter(bootconfig_path);
+ bootconfig_hash_footer_cmd.AddParameter("--partition_size");
+ bootconfig_hash_footer_cmd.AddParameter(bootconfig_size_bytes);
+ bootconfig_hash_footer_cmd.AddParameter("--partition_name");
+ bootconfig_hash_footer_cmd.AddParameter("bootconfig");
+ bootconfig_hash_footer_cmd.AddParameter("--key");
+ bootconfig_hash_footer_cmd.AddParameter(
+ DefaultHostArtifactsPath("etc/cvd_avb_testkey.pem"));
+ bootconfig_hash_footer_cmd.AddParameter("--algorithm");
+ bootconfig_hash_footer_cmd.AddParameter("SHA256_RSA4096");
+ int success = bootconfig_hash_footer_cmd.Start().Wait();
+ if (success != 0) {
+ LOG(ERROR) << "Unable to run append hash footer. Exited with status "
+ << success;
+ return false;
}
} else {
- // Repack the vendor boot image to add the instance specific bootconfig
- // parameters
- bool success = RepackVendorBootImage(
- std::string(), FLAGS_vendor_boot_image, new_vendor_boot_image_path,
- config->assembly_dir(), instance.instance_dir(), boot_config_vector,
- config->bootconfig_supported());
- CHECK(success) << "Failed to regenerate the vendor boot image";
+ const off_t bootconfig_size_bytes_gem5 = AlignToPowerOf2(
+ bytesWritten, PARTITION_SIZE_SHIFT);
+ bootconfig_fd->Truncate(bootconfig_size_bytes_gem5);
+ bootconfig_fd->Close();
}
+ return true;
}
+
+ const CuttlefishConfig& config_;
+ const CuttlefishConfig::InstanceSpecific& instance_;
+};
+
+class GeneratePersistentVbmeta : public SetupFeature {
+ public:
+ INJECT(GeneratePersistentVbmeta(
+ const CuttlefishConfig& config,
+ const CuttlefishConfig::InstanceSpecific& instance,
+ InitBootloaderEnvPartition& bootloader_env,
+ GeneratePersistentBootconfig& bootconfig))
+ : config_(config),
+ instance_(instance),
+ bootloader_env_(bootloader_env),
+ bootconfig_(bootconfig) {}
+
+ // SetupFeature
+ std::string Name() const override {
+ return "GeneratePersistentVbmeta";
+ }
+ bool Enabled() const override {
+ return (!config_.protected_vm());
+ }
+
+ private:
+ std::unordered_set<SetupFeature*> Dependencies() const override {
+ return {
+ static_cast<SetupFeature*>(&bootloader_env_),
+ static_cast<SetupFeature*>(&bootconfig_),
+ };
+ }
+
+ bool Setup() override {
+ auto avbtool_path = HostBinaryPath("avbtool");
+ Command vbmeta_cmd(avbtool_path);
+ vbmeta_cmd.AddParameter("make_vbmeta_image");
+ vbmeta_cmd.AddParameter("--output");
+ vbmeta_cmd.AddParameter(instance_.vbmeta_path());
+ vbmeta_cmd.AddParameter("--algorithm");
+ vbmeta_cmd.AddParameter("SHA256_RSA4096");
+ vbmeta_cmd.AddParameter("--key");
+ vbmeta_cmd.AddParameter(
+ DefaultHostArtifactsPath("etc/cvd_avb_testkey.pem"));
+
+ vbmeta_cmd.AddParameter("--chain_partition");
+ vbmeta_cmd.AddParameter("uboot_env:1:" +
+ DefaultHostArtifactsPath("etc/cvd.avbpubkey"));
+
+ if (config_.bootconfig_supported()) {
+ vbmeta_cmd.AddParameter("--chain_partition");
+ vbmeta_cmd.AddParameter("bootconfig:2:" +
+ DefaultHostArtifactsPath("etc/cvd.avbpubkey"));
+ }
+
+ bool success = vbmeta_cmd.Start().Wait();
+ if (success != 0) {
+ LOG(ERROR) << "Unable to create persistent vbmeta. Exited with status "
+ << success;
+ return false;
+ }
+
+ if (FileSize(instance_.vbmeta_path()) > VBMETA_MAX_SIZE) {
+ LOG(ERROR) << "Generated vbmeta - " << instance_.vbmeta_path()
+ << " is larger than the expected " << VBMETA_MAX_SIZE
+ << ". Stopping.";
+ return false;
+ }
+ if (FileSize(instance_.vbmeta_path()) != VBMETA_MAX_SIZE) {
+ auto fd = SharedFD::Open(instance_.vbmeta_path(), O_RDWR);
+ if (!fd->IsOpen() || fd->Truncate(VBMETA_MAX_SIZE) != 0) {
+ LOG(ERROR) << "`truncate --size=" << VBMETA_MAX_SIZE << " "
+ << instance_.vbmeta_path() << "` "
+ << "failed: " << fd->StrError();
+ return false;
+ }
+ }
+ return true;
+ }
+
+ const CuttlefishConfig& config_;
+ const CuttlefishConfig::InstanceSpecific& instance_;
+ InitBootloaderEnvPartition& bootloader_env_;
+ GeneratePersistentBootconfig& bootconfig_;
+};
+
+class InitializeMetadataImage : public SetupFeature {
+ public:
+ INJECT(InitializeMetadataImage()) {}
+
+ // SetupFeature
+ std::string Name() const override { return "InitializeMetadataImage"; }
+ bool Enabled() const override { return true; }
+
+ private:
+ std::unordered_set<SetupFeature*> Dependencies() const override { return {}; }
+ Result<void> ResultSetup() override {
+ if (FileExists(FLAGS_metadata_image)) {
+ return {};
+ }
+
+ CF_EXPECT(CreateBlankImage(FLAGS_metadata_image,
+ FLAGS_blank_metadata_image_mb, "none"),
+ "Failed to create \"" << FLAGS_metadata_image << "\" with size "
+ << FLAGS_blank_metadata_image_mb);
+ return {};
+ }
+};
+
+class InitializeAccessKregistryImage : public SetupFeature {
+ public:
+ INJECT(InitializeAccessKregistryImage(
+ const CuttlefishConfig& config,
+ const CuttlefishConfig::InstanceSpecific& instance))
+ : config_(config), instance_(instance) {}
+
+ // SetupFeature
+ std::string Name() const override { return "InitializeAccessKregistryImage"; }
+ bool Enabled() const override { return !config_.protected_vm(); }
+
+ private:
+ std::unordered_set<SetupFeature*> Dependencies() const override { return {}; }
+ Result<void> ResultSetup() override {
+ auto access_kregistry = instance_.access_kregistry_path();
+ if (FileExists(access_kregistry)) {
+ return {};
+ }
+ CF_EXPECT(CreateBlankImage(access_kregistry, 2 /* mb */, "none"),
+ "Failed to create \"" << access_kregistry << "\"");
+ return {};
+ }
+
+ const CuttlefishConfig& config_;
+ const CuttlefishConfig::InstanceSpecific& instance_;
+};
+
+class InitializeHwcomposerPmemImage : public SetupFeature {
+ public:
+ INJECT(InitializeHwcomposerPmemImage(
+ const CuttlefishConfig& config,
+ const CuttlefishConfig::InstanceSpecific& instance))
+ : config_(config), instance_(instance) {}
+
+ // SetupFeature
+ std::string Name() const override { return "InitializeHwcomposerPmemImage"; }
+ bool Enabled() const override { return !config_.protected_vm(); }
+
+ private:
+ std::unordered_set<SetupFeature*> Dependencies() const override { return {}; }
+ Result<void> ResultSetup() override {
+ if (FileExists(instance_.hwcomposer_pmem_path())) {
+ return {};
+ }
+ CF_EXPECT(
+ CreateBlankImage(instance_.hwcomposer_pmem_path(), 2 /* mb */, "none"),
+ "Failed creating \"" << instance_.hwcomposer_pmem_path() << "\"");
+ return {};
+ }
+
+ const CuttlefishConfig& config_;
+ const CuttlefishConfig::InstanceSpecific& instance_;
+};
+
+class InitializePstore : public SetupFeature {
+ public:
+ INJECT(InitializePstore(const CuttlefishConfig& config,
+ const CuttlefishConfig::InstanceSpecific& instance))
+ : config_(config), instance_(instance) {}
+
+ // SetupFeature
+ std::string Name() const override { return "InitializePstore"; }
+ bool Enabled() const override { return !config_.protected_vm(); }
+
+ private:
+ std::unordered_set<SetupFeature*> Dependencies() const override { return {}; }
+ Result<void> ResultSetup() override {
+ if (FileExists(instance_.pstore_path())) {
+ return {};
+ }
+
+ CF_EXPECT(CreateBlankImage(instance_.pstore_path(), 2 /* mb */, "none"),
+ "Failed to create \"" << instance_.pstore_path() << "\"");
+ return {};
+ }
+
+ const CuttlefishConfig& config_;
+ const CuttlefishConfig::InstanceSpecific& instance_;
+};
+
+class InitializeSdCard : public SetupFeature {
+ public:
+ INJECT(InitializeSdCard(const CuttlefishConfig& config,
+ const CuttlefishConfig::InstanceSpecific& instance))
+ : config_(config), instance_(instance) {}
+
+ // SetupFeature
+ std::string Name() const override { return "InitializeSdCard"; }
+ bool Enabled() const override {
+ return FLAGS_use_sdcard && !config_.protected_vm();
+ }
+
+ private:
+ std::unordered_set<SetupFeature*> Dependencies() const override { return {}; }
+ Result<void> ResultSetup() override {
+ if (FileExists(instance_.sdcard_path())) {
+ return {};
+ }
+ CF_EXPECT(CreateBlankImage(instance_.sdcard_path(),
+ FLAGS_blank_sdcard_image_mb, "sdcard"),
+ "Failed to create \"" << instance_.sdcard_path() << "\"");
+ return {};
+ }
+
+ const CuttlefishConfig& config_;
+ const CuttlefishConfig::InstanceSpecific& instance_;
+};
+
+class InitializeFactoryResetProtected : public SetupFeature {
+ public:
+ INJECT(InitializeFactoryResetProtected(
+ const CuttlefishConfig& config,
+ const CuttlefishConfig::InstanceSpecific& instance))
+ : config_(config), instance_(instance) {}
+
+ // SetupFeature
+ std::string Name() const override { return "InitializeSdCard"; }
+ bool Enabled() const override { return !config_.protected_vm(); }
+
+ private:
+ std::unordered_set<SetupFeature*> Dependencies() const override { return {}; }
+ Result<void> ResultSetup() override {
+ auto frp = instance_.factory_reset_protected_path();
+ if (FileExists(frp)) {
+ return {};
+ }
+ CF_EXPECT(CreateBlankImage(frp, 1 /* mb */, "none"),
+ "Failed to create \"" << frp << "\"");
+ return {};
+ }
+
+ const CuttlefishConfig& config_;
+ const CuttlefishConfig::InstanceSpecific& instance_;
+};
+
+class InitializeInstanceCompositeDisk : public SetupFeature {
+ public:
+ INJECT(InitializeInstanceCompositeDisk(
+ const CuttlefishConfig& config,
+ const CuttlefishConfig::InstanceSpecific& instance,
+ InitializeFactoryResetProtected& frp,
+ GeneratePersistentVbmeta& vbmeta))
+ : config_(config),
+ instance_(instance),
+ frp_(frp),
+ vbmeta_(vbmeta) {}
+
+ std::string Name() const override {
+ return "InitializeInstanceCompositeDisk";
+ }
+ bool Enabled() const override { return true; }
+
+ private:
+ std::unordered_set<SetupFeature*> Dependencies() const override {
+ return {
+ static_cast<SetupFeature*>(&frp_),
+ static_cast<SetupFeature*>(&vbmeta_),
+ };
+ }
+ Result<void> ResultSetup() override {
+ auto ipath = [this](const std::string& path) -> std::string {
+ return instance_.PerInstancePath(path.c_str());
+ };
+ auto persistent_disk_builder =
+ DiskBuilder()
+ .Partitions(persistent_composite_disk_config(config_, instance_))
+ .VmManager(config_.vm_manager())
+ .CrosvmPath(config_.crosvm_binary())
+ .ConfigPath(ipath("persistent_composite_disk_config.txt"))
+ .HeaderPath(ipath("persistent_composite_gpt_header.img"))
+ .FooterPath(ipath("persistent_composite_gpt_footer.img"))
+ .CompositeDiskPath(instance_.persistent_composite_disk_path())
+ .ResumeIfPossible(FLAGS_resume);
+
+ CF_EXPECT(persistent_disk_builder.BuildCompositeDiskIfNecessary());
+ return {};
+ }
+
+ const CuttlefishConfig& config_;
+ const CuttlefishConfig::InstanceSpecific& instance_;
+ InitializeFactoryResetProtected& frp_;
+ GeneratePersistentVbmeta& vbmeta_;
+};
+
+class VbmetaEnforceMinimumSize : public SetupFeature {
+ public:
+ INJECT(VbmetaEnforceMinimumSize()) {}
+
+ std::string Name() const override { return "VbmetaEnforceMinimumSize"; }
+ bool Enabled() const override { return true; }
+
+ private:
+ std::unordered_set<SetupFeature*> Dependencies() const override { return {}; }
+ Result<void> ResultSetup() override {
+ // libavb expects to be able to read the maximum vbmeta size, so we must
+ // provide a partition which matches this or the read will fail
+ for (const auto& vbmeta_image :
+ {FLAGS_vbmeta_image, FLAGS_vbmeta_system_image}) {
+ if (FileSize(vbmeta_image) != VBMETA_MAX_SIZE) {
+ auto fd = SharedFD::Open(vbmeta_image, O_RDWR);
+ CF_EXPECT(fd->IsOpen(), "Could not open \"" << vbmeta_image << "\": "
+ << fd->StrError());
+ CF_EXPECT(fd->Truncate(VBMETA_MAX_SIZE) == 0,
+ "`truncate --size=" << VBMETA_MAX_SIZE << " " << vbmeta_image
+ << "` failed: " << fd->StrError());
+ }
+ }
+ return {};
+ }
+};
+
+class BootloaderPresentCheck : public SetupFeature {
+ public:
+ INJECT(BootloaderPresentCheck()) {}
+
+ std::string Name() const override { return "BootloaderPresentCheck"; }
+ bool Enabled() const override { return true; }
+
+ private:
+ std::unordered_set<SetupFeature*> Dependencies() const override { return {}; }
+ Result<void> ResultSetup() override {
+ CF_EXPECT(FileHasContent(FLAGS_bootloader),
+ "File not found: " << FLAGS_bootloader);
+ return {};
+ }
+};
+
+static fruit::Component<> DiskChangesComponent(const FetcherConfig* fetcher,
+ const CuttlefishConfig* config) {
+ return fruit::createComponent()
+ .bindInstance(*fetcher)
+ .bindInstance(*config)
+ .addMultibinding<SetupFeature, InitializeMetadataImage>()
+ .addMultibinding<SetupFeature, BootImageRepacker>()
+ .addMultibinding<SetupFeature, VbmetaEnforceMinimumSize>()
+ .addMultibinding<SetupFeature, BootloaderPresentCheck>()
+ .addMultibinding<SetupFeature, Gem5ImageUnpacker>()
+ .install(FixedMiscImagePathComponent, &FLAGS_misc_image)
+ .install(InitializeMiscImageComponent)
+ .install(FixedDataImagePathComponent, &FLAGS_data_image)
+ .install(InitializeDataImageComponent)
+ // Create esp if necessary
+ .install(InitializeEspImageComponent, &FLAGS_otheros_esp_image,
+ &FLAGS_otheros_kernel_path, &FLAGS_otheros_initramfs_path,
+ &FLAGS_otheros_root_image, config)
+ .install(SuperImageRebuilderComponent, &FLAGS_super_image);
}
-void CreateDynamicDiskFiles(const FetcherConfig& fetcher_config,
- const CuttlefishConfig* config) {
- // Create misc if necessary
- CHECK(InitializeMiscImage(FLAGS_misc_image)) << "Failed to create misc image";
+static fruit::Component<> DiskChangesPerInstanceComponent(
+ const FetcherConfig* fetcher, const CuttlefishConfig* config,
+ const CuttlefishConfig::InstanceSpecific* instance) {
+ return fruit::createComponent()
+ .bindInstance(*fetcher)
+ .bindInstance(*config)
+ .bindInstance(*instance)
+ .addMultibinding<SetupFeature, InitializeAccessKregistryImage>()
+ .addMultibinding<SetupFeature, InitializeHwcomposerPmemImage>()
+ .addMultibinding<SetupFeature, InitializePstore>()
+ .addMultibinding<SetupFeature, InitializeSdCard>()
+ .addMultibinding<SetupFeature, InitializeFactoryResetProtected>()
+ .addMultibinding<SetupFeature, GeneratePersistentBootconfig>()
+ .addMultibinding<SetupFeature, GeneratePersistentVbmeta>()
+ .addMultibinding<SetupFeature, InitializeInstanceCompositeDisk>()
+ .install(InitBootloaderEnvPartitionComponent);
+}
- // Create data if necessary
- DataImageResult dataImageResult = ApplyDataImagePolicy(*config, FLAGS_data_image);
- CHECK(dataImageResult != DataImageResult::Error) << "Failed to set up userdata";
+Result<void> CreateDynamicDiskFiles(const FetcherConfig& fetcher_config,
+ const CuttlefishConfig& config) {
+ // TODO(schuffelen): Unify this with the other injector created in
+ // assemble_cvd.cpp
+ fruit::Injector<> injector(DiskChangesComponent, &fetcher_config, &config);
- if (!FileExists(FLAGS_metadata_image)) {
- CreateBlankImage(FLAGS_metadata_image, FLAGS_blank_metadata_image_mb, "none");
+ const auto& features = injector.getMultibindings<SetupFeature>();
+ CF_EXPECT(SetupFeature::RunSetup(features));
+
+ for (const auto& instance : config.Instances()) {
+ fruit::Injector<> instance_injector(DiskChangesPerInstanceComponent,
+ &fetcher_config, &config, &instance);
+ const auto& instance_features =
+ instance_injector.getMultibindings<SetupFeature>();
+ CF_EXPECT(SetupFeature::RunSetup(instance_features),
+ "instance = \"" << instance.instance_name() << "\"");
}
- // If we are booting a protected VM, for now, assume we want a super minimal
- // environment with no userdata encryption, limited debug, no FRP emulation, a
- // static env for the bootloader, no SD-Card and no resume-on-reboot HAL
- // support. We can also assume that image repacking isn't trusted. Repacking
- // requires resigning the image and keys from an android host aren't trusted.
- if (!FLAGS_protected_vm) {
- RepackAllBootImages(config);
-
- for (const auto& instance : config->Instances()) {
- if (!FileExists(instance.access_kregistry_path())) {
- CreateBlankImage(instance.access_kregistry_path(), 2 /* mb */, "none");
- }
-
- if (!FileExists(instance.pstore_path())) {
- CreateBlankImage(instance.pstore_path(), 2 /* mb */, "none");
- }
-
- if (FLAGS_use_sdcard && !FileExists(instance.sdcard_path())) {
- CreateBlankImage(instance.sdcard_path(),
- FLAGS_blank_sdcard_image_mb, "sdcard");
- }
-
- CHECK(InitBootloaderEnvPartition(*config, instance))
- << "Failed to create bootloader environment partition";
-
- const auto frp = instance.factory_reset_protected_path();
- if (!FileExists(frp)) {
- CreateBlankImage(frp, 1 /* mb */, "none");
- }
-
- const auto bootconfig_path = instance.persistent_bootconfig_path();
- if (!FileExists(bootconfig_path)) {
- CreateBlankImage(bootconfig_path, 1 /* mb */, "none");
- }
-
- auto bootconfig_fd = SharedFD::Open(bootconfig_path, O_RDWR);
- CHECK(bootconfig_fd->IsOpen())
- << "Unable to open bootconfig file: " << bootconfig_fd->StrError();
-
- const std::string bootconfig =
- android::base::Join(BootconfigArgsFromConfig(*config, instance),
- "\n") +
- "\n";
- ssize_t bytesWritten = WriteAll(bootconfig_fd, bootconfig);
- CHECK(bytesWritten == bootconfig.size());
- LOG(DEBUG)
- << "Bootconfig parameters from vendor boot image and config are "
- << ReadFile(bootconfig_path);
-
- const off_t bootconfig_size_bytes =
- AlignToPowerOf2(bootconfig.size(), PARTITION_SIZE_SHIFT);
- CHECK(bootconfig_fd->Truncate(bootconfig_size_bytes) == 0)
- << "`truncate --size=" << bootconfig_size_bytes << " bytes "
- << bootconfig_path << "` failed:" << bootconfig_fd->StrError();
- }
+ // Check if filling in the sparse image would run out of disk space.
+ auto existing_sizes = SparseFileSizes(FLAGS_data_image);
+ CF_EXPECT(existing_sizes.sparse_size > 0 || existing_sizes.disk_size > 0,
+ "Unable to determine size of \"" << FLAGS_data_image
+ << "\". Does this file exist?");
+ auto available_space = AvailableSpaceAtPath(FLAGS_data_image);
+ if (available_space < existing_sizes.sparse_size - existing_sizes.disk_size) {
+ // TODO(schuffelen): Duplicate this check in run_cvd when it can run on a
+ // separate machine
+ return CF_ERR("Not enough space remaining in fs containing \""
+ << FLAGS_data_image << "\", wanted "
+ << (existing_sizes.sparse_size - existing_sizes.disk_size)
+ << ", got " << available_space);
+ } else {
+ LOG(DEBUG) << "Available space: " << available_space;
+ LOG(DEBUG) << "Sparse size of \"" << FLAGS_data_image
+ << "\": " << existing_sizes.sparse_size;
+ LOG(DEBUG) << "Disk size of \"" << FLAGS_data_image
+ << "\": " << existing_sizes.disk_size;
}
- for (const auto& instance : config->Instances()) {
- bool compositeMatchesDiskConfig = DoesCompositeMatchCurrentDiskConfig(
- instance.PerInstancePath("persistent_composite_disk_config.txt"),
- persistent_composite_disk_config(instance));
- bool oldCompositeDisk =
- ShouldCreateCompositeDisk(instance.persistent_composite_disk_path(),
- persistent_composite_disk_config(instance));
-
- if (!compositeMatchesDiskConfig || oldCompositeDisk) {
- CHECK(CreatePersistentCompositeDisk(*config, instance))
- << "Failed to create persistent composite disk";
- }
- }
-
- // libavb expects to be able to read the maximum vbmeta size, so we must
- // provide a partition which matches this or the read will fail
- for (const auto& vbmeta_image : { FLAGS_vbmeta_image, FLAGS_vbmeta_system_image }) {
- if (FileSize(vbmeta_image) != VBMETA_MAX_SIZE) {
- auto fd = SharedFD::Open(vbmeta_image, O_RDWR);
- CHECK(fd->Truncate(VBMETA_MAX_SIZE) == 0)
- << "`truncate --size=" << VBMETA_MAX_SIZE << " " << vbmeta_image << "` "
- << "failed: " << fd->StrError();
- }
- }
-
- CHECK(FileHasContent(FLAGS_bootloader))
- << "File not found: " << FLAGS_bootloader;
-
- if (!FLAGS_esp.empty()) {
- CHECK(FileHasContent(FLAGS_esp))
- << "File not found: " << FLAGS_esp;
- }
-
- if (SuperImageNeedsRebuilding(fetcher_config, *config)) {
- bool success = RebuildSuperImage(fetcher_config, *config, FLAGS_super_image);
- CHECK(success) << "Super image rebuilding requested but could not be completed.";
- }
-
- bool newDataImage = dataImageResult == DataImageResult::FileUpdated;
-
- for (auto instance : config->Instances()) {
- bool compositeMatchesDiskConfig = DoesCompositeMatchCurrentDiskConfig(
- instance.PerInstancePath("os_composite_disk_config.txt"),
- os_composite_disk_config(instance));
- bool oldCompositeDisk = ShouldCreateCompositeDisk(
- instance.os_composite_disk_path(), os_composite_disk_config(instance));
- if (!compositeMatchesDiskConfig || oldCompositeDisk || !FLAGS_resume || newDataImage) {
- if (FLAGS_resume) {
- LOG(INFO) << "Requested to continue an existing session, (the default) "
- << "but the disk files have become out of date. Wiping the "
- << "old session files and starting a new session for device "
- << instance.serial_number();
- }
- CHECK(CreateCompositeDisk(*config, instance))
- << "Failed to create composite disk";
+ auto os_disk_builder = OsCompositeDiskBuilder(config);
+ auto built_composite =
+ CF_EXPECT(os_disk_builder.BuildCompositeDiskIfNecessary());
+ if (built_composite) {
+ for (auto instance : config.Instances()) {
if (FileExists(instance.access_kregistry_path())) {
- CreateBlankImage(instance.access_kregistry_path(), 2 /* mb */, "none");
+ CF_EXPECT(CreateBlankImage(instance.access_kregistry_path(), 2 /* mb */,
+ "none"),
+ "Failed for \"" << instance.access_kregistry_path() << "\"");
+ }
+ if (FileExists(instance.hwcomposer_pmem_path())) {
+ CF_EXPECT(CreateBlankImage(instance.hwcomposer_pmem_path(), 2 /* mb */,
+ "none"),
+ "Failed for \"" << instance.hwcomposer_pmem_path() << "\"");
}
if (FileExists(instance.pstore_path())) {
- CreateBlankImage(instance.pstore_path(), 2 /* mb */, "none");
+ CF_EXPECT(CreateBlankImage(instance.pstore_path(), 2 /* mb */, "none"),
+ "Failed for\"" << instance.pstore_path() << "\"");
}
}
}
if (!FLAGS_protected_vm) {
- for (auto instance : config->Instances()) {
- auto overlay_path = instance.PerInstancePath("overlay.img");
- bool missingOverlay = !FileExists(overlay_path);
- bool newOverlay = FileModificationTime(overlay_path) <
- FileModificationTime(instance.os_composite_disk_path());
- if (missingOverlay || !FLAGS_resume || newOverlay) {
- CreateQcowOverlay(config->crosvm_binary(),
- instance.os_composite_disk_path(), overlay_path);
+ for (auto instance : config.Instances()) {
+ os_disk_builder.OverlayPath(instance.PerInstancePath("overlay.img"));
+ CF_EXPECT(os_disk_builder.BuildOverlayIfNecessary());
+ if (instance.start_ap()) {
+ os_disk_builder.OverlayPath(instance.PerInstancePath("ap_overlay.img"));
+ CF_EXPECT(os_disk_builder.BuildOverlayIfNecessary());
}
}
}
- for (auto instance : config->Instances()) {
+ for (auto instance : config.Instances()) {
// Check that the files exist
for (const auto& file : instance.virtual_disk_paths()) {
if (!file.empty()) {
- CHECK(FileHasContent(file)) << "File not found: " << file;
+ CF_EXPECT(FileHasContent(file), "File not found: \"" << file << "\"");
}
}
+ // Gem5 Simulate per-instance what the bootloader would usually do
+ // Since on other devices this runs every time, just do it here every time
+ if (config.vm_manager() == Gem5Manager::name()) {
+ RepackGem5BootImage(
+ instance.PerInstancePath("initrd.img"),
+ instance.persistent_bootconfig_path(),
+ config.assembly_dir());
+ }
}
+
+ return {};
}
} // namespace cuttlefish
diff --git a/host/commands/assemble_cvd/disk_flags.h b/host/commands/assemble_cvd/disk_flags.h
index 3491c3a..e75aa4f 100644
--- a/host/commands/assemble_cvd/disk_flags.h
+++ b/host/commands/assemble_cvd/disk_flags.h
@@ -20,14 +20,19 @@
#include <memory>
#include <vector>
+#include "common/libs/utils/result.h"
+#include "host/commands/assemble_cvd/disk_builder.h"
#include "host/libs/config/cuttlefish_config.h"
#include "host/libs/config/fetcher_config.h"
+#include "host/libs/image_aggregator/image_aggregator.h"
namespace cuttlefish {
-bool ResolveInstanceFiles();
-bool ShouldCreateAllCompositeDisks(const CuttlefishConfig& config);
-void CreateDynamicDiskFiles(const FetcherConfig& fetcher_config,
- const CuttlefishConfig* config);
+Result<void> ResolveInstanceFiles();
+
+Result<void> CreateDynamicDiskFiles(const FetcherConfig& fetcher_config,
+ const CuttlefishConfig& config);
+std::vector<ImagePartition> GetOsCompositeDiskConfig();
+DiskBuilder OsCompositeDiskBuilder(const CuttlefishConfig& config);
} // namespace cuttlefish
diff --git a/host/commands/assemble_cvd/flag_feature.cpp b/host/commands/assemble_cvd/flag_feature.cpp
new file mode 100644
index 0000000..a81c4a1
--- /dev/null
+++ b/host/commands/assemble_cvd/flag_feature.cpp
@@ -0,0 +1,84 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#include "host/commands/assemble_cvd/flag_feature.h"
+
+#include <android-base/strings.h>
+#include <fruit/fruit.h>
+#include <gflags/gflags.h>
+#include <string.h>
+#include <string>
+#include <unordered_set>
+#include <vector>
+
+#include "host/libs/config/feature.h"
+
+namespace cuttlefish {
+
+static std::string XmlEscape(const std::string& s) {
+ using android::base::StringReplace;
+ return StringReplace(StringReplace(s, "<", "<", true), ">", ">", true);
+}
+
+class ParseGflagsImpl : public ParseGflags {
+ public:
+ INJECT(ParseGflagsImpl(ConfigFlag& config)) : config_(config) {}
+
+ std::string Name() const override { return "ParseGflags"; }
+ std::unordered_set<FlagFeature*> Dependencies() const override {
+ return {static_cast<FlagFeature*>(&config_)};
+ }
+ bool Process(std::vector<std::string>& args) override {
+ std::string process_name = "assemble_cvd";
+ std::vector<char*> pseudo_argv = {process_name.data()};
+ for (auto& arg : args) {
+ pseudo_argv.push_back(arg.data());
+ }
+ int argc = pseudo_argv.size();
+ auto argv = pseudo_argv.data();
+ gflags::AllowCommandLineReparsing(); // Support future non-gflags flags
+ gflags::ParseCommandLineNonHelpFlags(&argc, &argv,
+ /* remove_flags */ false);
+ return true;
+ }
+ bool WriteGflagsCompatHelpXml(std::ostream& out) const override {
+ // Lifted from external/gflags/src/gflags_reporting.cc:ShowXMLOfFlags
+ std::vector<gflags::CommandLineFlagInfo> flags;
+ gflags::GetAllFlags(&flags);
+ for (const auto& flag : flags) {
+ // From external/gflags/src/gflags_reporting.cc:DescribeOneFlagInXML
+ out << "<flag>\n";
+ out << " <file>" << XmlEscape(flag.filename) << "</file>\n";
+ out << " <name>" << XmlEscape(flag.name) << "</name>\n";
+ out << " <meaning>" << XmlEscape(flag.description) << "</meaning>\n";
+ out << " <default>" << XmlEscape(flag.default_value) << "</default>\n";
+ out << " <current>" << XmlEscape(flag.current_value) << "</current>\n";
+ out << " <type>" << XmlEscape(flag.type) << "</type>\n";
+ out << "</flag>\n";
+ }
+ return true;
+ }
+
+ private:
+ ConfigFlag& config_;
+};
+
+fruit::Component<fruit::Required<ConfigFlag>, ParseGflags> GflagsComponent() {
+ return fruit::createComponent()
+ .bind<ParseGflags, ParseGflagsImpl>()
+ .addMultibinding<FlagFeature, ParseGflags>();
+}
+
+} // namespace cuttlefish
diff --git a/common/libs/utils/size_utils.cpp b/host/commands/assemble_cvd/flag_feature.h
similarity index 67%
copy from common/libs/utils/size_utils.cpp
copy to host/commands/assemble_cvd/flag_feature.h
index 9f25445..da91a3d 100644
--- a/common/libs/utils/size_utils.cpp
+++ b/host/commands/assemble_cvd/flag_feature.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2018 The Android Open Source Project
+ * Copyright (C) 2021 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -13,16 +13,17 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
+#pragma once
-#include "common/libs/utils/size_utils.h"
+#include <fruit/fruit.h>
-#include <unistd.h>
+#include "host/libs/config/config_flag.h"
+#include "host/libs/config/feature.h"
namespace cuttlefish {
-uint64_t AlignToPowerOf2(uint64_t val, uint8_t align_log) {
- uint64_t align = 1ULL << align_log;
- return ((val + (align - 1)) / align) * align;
-}
+class ParseGflags : public FlagFeature {};
+
+fruit::Component<fruit::Required<ConfigFlag>, ParseGflags> GflagsComponent();
} // namespace cuttlefish
diff --git a/host/commands/assemble_cvd/flags.cc b/host/commands/assemble_cvd/flags.cc
index b12dfd2..08c2b68 100644
--- a/host/commands/assemble_cvd/flags.cc
+++ b/host/commands/assemble_cvd/flags.cc
@@ -19,15 +19,19 @@
#include <sstream>
#include <unordered_map>
+#include <fruit/fruit.h>
+
#include "common/libs/utils/environment.h"
#include "common/libs/utils/files.h"
+#include "common/libs/utils/flag_parser.h"
#include "host/commands/assemble_cvd/alloc.h"
#include "host/commands/assemble_cvd/boot_config.h"
-#include "host/commands/assemble_cvd/clean.h"
#include "host/commands/assemble_cvd/disk_flags.h"
+#include "host/libs/config/config_flag.h"
#include "host/libs/config/host_tools_version.h"
#include "host/libs/graphics_detector/graphics_detector.h"
#include "host/libs/vm_manager/crosvm_manager.h"
+#include "host/libs/vm_manager/gem5_manager.h"
#include "host/libs/vm_manager/qemu_manager.h"
#include "host/libs/vm_manager/vm_manager.h"
@@ -36,12 +40,7 @@
using cuttlefish::StringFromEnv;
using cuttlefish::vm_manager::CrosvmManager;
using google::FlagSettingMode::SET_FLAGS_DEFAULT;
-
-DEFINE_string(config, "phone",
- "Config preset name. Will automatically set flag fields "
- "using the values from this file of presets. See "
- "device/google/cuttlefish/shared/config/config_*.json "
- "for possible values.");
+using google::FlagSettingMode::SET_FLAGS_VALUE;
DEFINE_int32(cpus, 2, "Virtual CPU count.");
DEFINE_string(data_policy, "use_existing", "How to handle userdata partition."
@@ -49,8 +48,6 @@
"'always_create'.");
DEFINE_int32(blank_data_image_mb, 0,
"The size of the blank data image to generate, MB.");
-DEFINE_string(blank_data_image_fmt, "f2fs",
- "The fs format for the blank data image. Used with mkfs.");
DEFINE_int32(gdb_port, 0,
"Port number to spawn kernel gdb on e.g. -gdb_port=1234. The"
"kernel must have been built with CONFIG_RANDOMIZE_BASE "
@@ -85,10 +82,12 @@
DEFINE_string(initramfs_path, "", "Path to the initramfs");
DEFINE_string(extra_kernel_cmdline, "",
"Additional flags to put on the kernel command line");
+DEFINE_string(extra_bootconfig_args, "",
+ "Space-separated list of extra bootconfig args. "
+ "Note: overwriting an existing bootconfig argument "
+ "requires ':=' instead of '='.");
DEFINE_bool(guest_enforce_security, true,
"Whether to run in enforcing mode (non permissive).");
-DEFINE_bool(guest_audit_security, true,
- "Whether to log security audits.");
DEFINE_int32(memory_mb, 0, "Total amount of memory available for guest, MB.");
DEFINE_string(serial_number, cuttlefish::ForCurrentInstance("CUTTLEFISHCVD"),
"Serial number to use for the device");
@@ -99,14 +98,23 @@
DEFINE_string(gpu_mode, cuttlefish::kGpuModeAuto,
"What gpu configuration to use, one of {auto, drm_virgl, "
"gfxstream, guest_swiftshader}");
+DEFINE_string(hwcomposer, cuttlefish::kHwComposerAuto,
+ "What hardware composer to use, one of {auto, drm, ranchu} ");
+DEFINE_string(gpu_capture_binary, "",
+ "Path to the GPU capture binary to use when capturing GPU traces"
+ "(ngfx, renderdoc, etc)");
+DEFINE_bool(enable_gpu_udmabuf,
+ false,
+ "Use the udmabuf driver for zero-copy virtio-gpu");
+DEFINE_bool(enable_gpu_angle,
+ false,
+ "Use ANGLE to provide GLES implementation (always true for"
+ " guest_swiftshader");
DEFINE_bool(deprecated_boot_completed, false, "Log boot completed message to"
" host kernel. This is only used during transition of our clients."
" Will be deprecated soon.");
-DEFINE_bool(start_vnc_server, false, "Whether to start the vnc server process. "
- "The VNC server runs at port 6443 + i for "
- "the vsoc-i user or CUTTLEFISH_INSTANCE=i, "
- "starting from 1.");
+
DEFINE_bool(use_allocd, false,
"Acquire static resources from the resource allocator daemon.");
DEFINE_bool(enable_minimal_mode, false,
@@ -173,13 +181,17 @@
false,
"[Experimental] If enabled, exposes local adb service through a websocket.");
+static constexpr auto HOST_OPERATOR_SOCKET_PATH = "/run/cuttlefish/operator";
+
DEFINE_bool(
- start_webrtc_sig_server, false,
+ // The actual default for this flag is set with SetCommandLineOption() in
+ // GetKernelConfigsAndSetDefaults() at the end of this file.
+ start_webrtc_sig_server, true,
"Whether to start the webrtc signaling server. This option only applies to "
"the first instance, if multiple instances are launched they'll share the "
"same signaling server, which is owned by the first one.");
-DEFINE_string(webrtc_sig_server_addr, "0.0.0.0",
+DEFINE_string(webrtc_sig_server_addr, "",
"The address of the webrtc signaling server.");
DEFINE_int32(
@@ -202,9 +214,13 @@
"The path section of the URL where the device should be "
"registered with the signaling server.");
+DEFINE_bool(webrtc_sig_server_secure, true,
+ "Whether the WebRTC signaling server uses secure protocols (WSS vs WS).");
+
DEFINE_bool(verify_sig_server_certificate, false,
"Whether to verify the signaling server's certificate with a "
- "trusted signing authority (Disallow self signed certificates).");
+ "trusted signing authority (Disallow self signed certificates). "
+ "This is ignored if an insecure server is configured.");
DEFINE_string(sig_server_headers_file, "",
"Path to a file containing HTTP headers to be included in the "
@@ -217,25 +233,12 @@
"appearance of the substring '{num}' in the device id will be substituted "
"with the instance number to support multiple instances");
-DEFINE_string(adb_mode, "vsock_half_tunnel",
- "Mode for ADB connection."
- "'vsock_tunnel' for a TCP connection tunneled through vsock, "
- "'native_vsock' for a direct connection to the guest ADB over "
- "vsock, 'vsock_half_tunnel' for a TCP connection forwarded to "
- "the guest ADB server, or a comma separated list of types as in "
- "'native_vsock,vsock_half_tunnel'");
-DEFINE_bool(run_adb_connector, !cuttlefish::IsRunningInContainer(),
- "Maintain adb connection by sending 'adb connect' commands to the "
- "server. Only relevant with -adb_mode=tunnel or vsock_tunnel");
-
DEFINE_string(uuid, cuttlefish::ForCurrentInstance(cuttlefish::kDefaultUuidPrefix),
"UUID to use for the device. Random if not specified");
DEFINE_bool(daemon, false,
"Run cuttlefish in background, the launcher exits on boot "
"completed/failed");
-DEFINE_string(device_title, "", "Human readable name for the instance, "
- "used by the vnc_server for its server title");
DEFINE_string(setupwizard_mode, "DISABLED",
"One of DISABLED,OPTIONAL,REQUIRED");
@@ -243,21 +246,11 @@
"Path to the directory containing the qemu binary to use");
DEFINE_string(crosvm_binary, HostBinaryPath("crosvm"),
"The Crosvm binary to use");
-DEFINE_string(tpm_device, "", "A host TPM device to pass through commands to.");
+DEFINE_string(gem5_binary_dir, HostBinaryPath("gem5"),
+ "Path to the gem5 build tree root");
DEFINE_bool(restart_subprocesses, true, "Restart any crashed host process");
DEFINE_bool(enable_vehicle_hal_grpc_server, true, "Enables the vehicle HAL "
"emulation gRPC server on the host");
-DEFINE_string(custom_action_config, "",
- "Path to a custom action config JSON. Defaults to the file provided by "
- "build variable CVD_CUSTOM_ACTION_CONFIG. If this build variable "
- "is empty then the custom action config will be empty as well.");
-DEFINE_string(custom_actions, "",
- "Serialized JSON of an array of custom action objects (in the "
- "same format as custom action config JSON files). For use "
- "within --config preset config files; prefer "
- "--custom_action_config to specify a custom config file on the "
- "command line. Actions in this flag are combined with actions "
- "in --custom_action_config.");
DEFINE_string(bootloader, "", "Bootloader binary path");
DEFINE_string(boot_slot, "", "Force booting into the given slot. If empty, "
"the slot will be chosen based on the misc partition if using a "
@@ -287,6 +280,21 @@
DEFINE_bool(vhost_net, false, "Enable vhost acceleration of networking");
+DEFINE_string(
+ vhost_user_mac80211_hwsim, "",
+ "Unix socket path for vhost-user of mac80211_hwsim, typically served by "
+ "wmediumd. You can set this when using an external wmediumd instance.");
+DEFINE_string(wmediumd_config, "",
+ "Path to the wmediumd config file. When missing, the default "
+ "configuration is used which adds MAC addresses for up to 16 "
+ "cuttlefish instances including AP.");
+DEFINE_string(ap_rootfs_image,
+ DefaultHostArtifactsPath("etc/openwrt/images/openwrt_rootfs"),
+ "rootfs image for AP instance");
+DEFINE_string(ap_kernel_image,
+ DefaultHostArtifactsPath("etc/openwrt/images/kernel_for_openwrt"),
+ "kernel image for AP instance");
+
DEFINE_bool(record_screen, false, "Enable screen recording. "
"Requires --start_webrtc");
@@ -321,20 +329,19 @@
DEFINE_uint32(camera_server_port, 0, "camera vsock port");
+DEFINE_string(userdata_format, "f2fs", "The userdata filesystem format");
+
DECLARE_string(assembly_dir);
DECLARE_string(boot_image);
DECLARE_string(system_image_dir);
namespace cuttlefish {
using vm_manager::QemuManager;
+using vm_manager::Gem5Manager;
using vm_manager::GetVmManager;
namespace {
-bool IsFlagSet(const std::string& flag) {
- return !gflags::GetCommandLineFlagInfoOrDie(flag.c_str()).is_default;
-}
-
std::pair<uint16_t, uint16_t> ParsePortRange(const std::string& flag) {
static const std::regex rgx("[0-9]+:[0-9]+");
CHECK(std::regex_match(flag, rgx))
@@ -348,11 +355,6 @@
return port_range;
}
-int NumStreamers() {
- auto start_flags = {FLAGS_start_vnc_server, FLAGS_start_webrtc};
- return std::count(start_flags.begin(), start_flags.end(), true);
-}
-
std::string StrForInstance(const std::string& prefix, int num) {
std::ostringstream stream;
stream << prefix << std::setfill('0') << std::setw(2) << num;
@@ -414,13 +416,15 @@
}
#ifdef __ANDROID__
-void ReadKernelConfig(KernelConfig* kernel_config) {
+Result<KernelConfig> ReadKernelConfig() {
// QEMU isn't on Android, so always follow host arch
- kernel_config->target_arch = HostArch();
- kernel_config->bootconfig_supported = true;
+ KernelConfig ret{};
+ ret.target_arch = HostArch();
+ ret.bootconfig_supported = true;
+ return ret;
}
#else
-void ReadKernelConfig(KernelConfig* kernel_config) {
+Result<KernelConfig> ReadKernelConfig() {
// extract-ikconfig can be called directly on the boot image since it looks
// for the ikconfig header in the image before extracting the config list.
// This code is liable to break if the boot image ever includes the
@@ -438,44 +442,50 @@
std::string ikconfig_path =
StringFromEnv("TEMP", "/tmp") + "/ikconfig.XXXXXX";
auto ikconfig_fd = SharedFD::Mkstemp(&ikconfig_path);
- CHECK(ikconfig_fd->IsOpen())
- << "Unable to create ikconfig file: " << ikconfig_fd->StrError();
+ CF_EXPECT(ikconfig_fd->IsOpen(),
+ "Unable to create ikconfig file: " << ikconfig_fd->StrError());
ikconfig_cmd.RedirectStdIO(Subprocess::StdIOChannel::kStdOut, ikconfig_fd);
auto ikconfig_proc = ikconfig_cmd.Start();
- CHECK(ikconfig_proc.Started() && ikconfig_proc.Wait() == 0)
- << "Failed to extract ikconfig from " << kernel_image_path;
+ CF_EXPECT(ikconfig_proc.Started() && ikconfig_proc.Wait() == 0,
+ "Failed to extract ikconfig from " << kernel_image_path);
std::string config = ReadFile(ikconfig_path);
+ KernelConfig kernel_config;
if (config.find("\nCONFIG_ARM=y") != std::string::npos) {
- kernel_config->target_arch = Arch::Arm;
+ kernel_config.target_arch = Arch::Arm;
} else if (config.find("\nCONFIG_ARM64=y") != std::string::npos) {
- kernel_config->target_arch = Arch::Arm64;
+ kernel_config.target_arch = Arch::Arm64;
} else if (config.find("\nCONFIG_X86_64=y") != std::string::npos) {
- kernel_config->target_arch = Arch::X86_64;
+ kernel_config.target_arch = Arch::X86_64;
} else if (config.find("\nCONFIG_X86=y") != std::string::npos) {
- kernel_config->target_arch = Arch::X86;
+ kernel_config.target_arch = Arch::X86;
} else {
- LOG(FATAL) << "Unknown target architecture";
+ return CF_ERR("Unknown target architecture");
}
- kernel_config->bootconfig_supported =
+ kernel_config.bootconfig_supported =
config.find("\nCONFIG_BOOT_CONFIG=y") != std::string::npos;
unlink(ikconfig_path.c_str());
+ return kernel_config;
}
#endif // #ifdef __ANDROID__
} // namespace
CuttlefishConfig InitializeCuttlefishConfiguration(
- const std::string& instance_dir, int modem_simulator_count,
- KernelConfig kernel_config) {
- // At most one streamer can be started.
- CHECK(NumStreamers() <= 1);
-
+ const std::string& root_dir, int modem_simulator_count,
+ KernelConfig kernel_config, fruit::Injector<>& injector) {
CuttlefishConfig tmp_config_obj;
- tmp_config_obj.set_assembly_dir(FLAGS_assembly_dir);
+
+ for (const auto& fragment : injector.getMultibindings<ConfigFragment>()) {
+ CHECK(tmp_config_obj.SaveFragment(*fragment))
+ << "Failed to save fragment " << fragment->Name();
+ }
+
+ tmp_config_obj.set_root_dir(root_dir);
+
tmp_config_obj.set_target_arch(kernel_config.target_arch);
tmp_config_obj.set_bootconfig_supported(kernel_config.bootconfig_supported);
auto vmm = GetVmManager(FLAGS_vm_manager, kernel_config.target_arch);
@@ -521,10 +531,9 @@
const GraphicsAvailability graphics_availability =
GetGraphicsAvailabilityWithSubprocessCheck();
- LOG(VERBOSE) << graphics_availability;
+ LOG(DEBUG) << graphics_availability;
tmp_config_obj.set_gpu_mode(FLAGS_gpu_mode);
-
if (tmp_config_obj.gpu_mode() != kGpuModeAuto &&
tmp_config_obj.gpu_mode() != kGpuModeDrmVirgl &&
tmp_config_obj.gpu_mode() != kGpuModeGfxStream &&
@@ -559,15 +568,57 @@
"--gpu_mode=auto or --gpu_mode=guest_swiftshader.";
}
}
+
+ tmp_config_obj.set_restart_subprocesses(FLAGS_restart_subprocesses);
+ tmp_config_obj.set_gpu_capture_binary(FLAGS_gpu_capture_binary);
+ if (!tmp_config_obj.gpu_capture_binary().empty()) {
+ CHECK(tmp_config_obj.gpu_mode() == kGpuModeGfxStream)
+ << "GPU capture only supported with --gpu_mode=gfxstream";
+
+ // GPU capture runs in a detached mode where the "launcher" process
+ // intentionally exits immediately.
+ CHECK(!tmp_config_obj.restart_subprocesses())
+ << "GPU capture only supported with --norestart_subprocesses";
+ }
+
+ tmp_config_obj.set_hwcomposer(FLAGS_hwcomposer);
+ if (!tmp_config_obj.hwcomposer().empty()) {
+ if (tmp_config_obj.hwcomposer() == kHwComposerRanchu) {
+ CHECK(tmp_config_obj.gpu_mode() != kGpuModeDrmVirgl)
+ << "ranchu hwcomposer not supported with --gpu_mode=drm_virgl";
+ }
+ }
+
+ if (tmp_config_obj.hwcomposer() == kHwComposerAuto) {
+ if (tmp_config_obj.gpu_mode() == kGpuModeDrmVirgl) {
+ tmp_config_obj.set_hwcomposer(kHwComposerDrm);
+ } else {
+ tmp_config_obj.set_hwcomposer(kHwComposerRanchu);
+ }
+ }
+
+ // The device needs to avoid having both hwcomposer2.4 and hwcomposer3
+ // services running at the same time so warn the user to manually build
+ // in drm_hwcomposer when needed.
+ if (tmp_config_obj.hwcomposer() == kHwComposerAuto) {
+ LOG(WARNING) << "In order to run with --hwcomposer=drm. Please make sure "
+ "Cuttlefish was built with "
+ "TARGET_ENABLE_DRMHWCOMPOSER=true.";
+ }
+
+ tmp_config_obj.set_enable_gpu_udmabuf(FLAGS_enable_gpu_udmabuf);
+ tmp_config_obj.set_enable_gpu_angle(FLAGS_enable_gpu_angle);
+
// Sepolicy rules need to be updated to support gpu mode. Temporarily disable
// auto-enabling sandbox when gpu is enabled (b/152323505).
if (tmp_config_obj.gpu_mode() != kGpuModeGuestSwiftshader) {
SetCommandLineOptionWithMode("enable_sandbox", "false", SET_FLAGS_DEFAULT);
}
- if (vmm->ConfigureGpuMode(tmp_config_obj.gpu_mode()).empty()) {
- LOG(FATAL) << "Invalid gpu_mode=" << FLAGS_gpu_mode <<
- " does not work with vm_manager=" << FLAGS_vm_manager;
+ if (vmm->ConfigureGraphics(tmp_config_obj).empty()) {
+ LOG(FATAL) << "Invalid (gpu_mode=," << FLAGS_gpu_mode <<
+ " hwcomposer= " << FLAGS_hwcomposer <<
+ ") does not work with vm_manager=" << FLAGS_vm_manager;
}
CHECK(!FLAGS_smt || FLAGS_cpus % 2 == 0)
@@ -585,12 +636,9 @@
tmp_config_obj.set_gdb_port(FLAGS_gdb_port);
- std::vector<std::string> adb = android::base::Split(FLAGS_adb_mode, ",");
- tmp_config_obj.set_adb_mode(std::set<std::string>(adb.begin(), adb.end()));
-
tmp_config_obj.set_guest_enforce_security(FLAGS_guest_enforce_security);
- tmp_config_obj.set_guest_audit_security(FLAGS_guest_audit_security);
tmp_config_obj.set_extra_kernel_cmdline(FLAGS_extra_kernel_cmdline);
+ tmp_config_obj.set_extra_bootconfig_args(FLAGS_extra_bootconfig_args);
if (FLAGS_console) {
SetCommandLineOptionWithMode("enable_sandbox", "false", SET_FLAGS_DEFAULT);
@@ -605,15 +653,14 @@
tmp_config_obj.set_qemu_binary_dir(FLAGS_qemu_binary_dir);
tmp_config_obj.set_crosvm_binary(FLAGS_crosvm_binary);
- tmp_config_obj.set_tpm_device(FLAGS_tpm_device);
-
- tmp_config_obj.set_enable_vnc_server(FLAGS_start_vnc_server);
+ tmp_config_obj.set_gem5_binary_dir(FLAGS_gem5_binary_dir);
tmp_config_obj.set_seccomp_policy_dir(FLAGS_seccomp_policy_dir);
tmp_config_obj.set_enable_webrtc(FLAGS_start_webrtc);
tmp_config_obj.set_webrtc_assets_dir(FLAGS_webrtc_assets_dir);
tmp_config_obj.set_webrtc_certs_dir(FLAGS_webrtc_certs_dir);
+ tmp_config_obj.set_sig_server_secure(FLAGS_webrtc_sig_server_secure);
// Note: This will be overridden if the sig server is started by us
tmp_config_obj.set_sig_server_port(FLAGS_webrtc_sig_server_port);
tmp_config_obj.set_sig_server_address(FLAGS_webrtc_sig_server_addr);
@@ -634,71 +681,15 @@
tmp_config_obj.set_webrtc_enable_adb_websocket(
FLAGS_webrtc_enable_adb_websocket);
- tmp_config_obj.set_restart_subprocesses(FLAGS_restart_subprocesses);
- tmp_config_obj.set_run_adb_connector(FLAGS_run_adb_connector);
tmp_config_obj.set_run_as_daemon(FLAGS_daemon);
tmp_config_obj.set_data_policy(FLAGS_data_policy);
tmp_config_obj.set_blank_data_image_mb(FLAGS_blank_data_image_mb);
- tmp_config_obj.set_blank_data_image_fmt(FLAGS_blank_data_image_fmt);
tmp_config_obj.set_enable_gnss_grpc_proxy(FLAGS_start_gnss_proxy);
- tmp_config_obj.set_enable_vehicle_hal_grpc_server(FLAGS_enable_vehicle_hal_grpc_server);
- tmp_config_obj.set_vehicle_hal_grpc_server_binary(
- HostBinaryPath("android.hardware.automotive.vehicle@2.0-virtualization-grpc-server"));
-
- std::string custom_action_config;
- if (!FLAGS_custom_action_config.empty()) {
- custom_action_config = FLAGS_custom_action_config;
- } else {
- std::string custom_action_config_dir =
- DefaultHostArtifactsPath("etc/cvd_custom_action_config");
- if (DirectoryExists(custom_action_config_dir)) {
- auto custom_action_configs = DirectoryContents(custom_action_config_dir);
- // Two entries are always . and ..
- if (custom_action_configs.size() > 3) {
- LOG(ERROR) << "Expected at most one custom action config in "
- << custom_action_config_dir << ". Please delete extras.";
- } else if (custom_action_configs.size() == 3) {
- for (const auto& config : custom_action_configs) {
- if (android::base::EndsWithIgnoreCase(config, ".json")) {
- custom_action_config = custom_action_config_dir + "/" + config;
- }
- }
- }
- }
- }
- std::vector<CustomActionConfig> custom_actions;
- Json::CharReaderBuilder builder;
- Json::Value custom_action_array(Json::arrayValue);
- if (custom_action_config != "") {
- // Load the custom action config JSON.
- std::ifstream ifs(custom_action_config);
- std::string errorMessage;
- if (!Json::parseFromStream(builder, ifs, &custom_action_array, &errorMessage)) {
- LOG(FATAL) << "Could not read custom actions config file "
- << custom_action_config << ": "
- << errorMessage;
- }
- for (const auto& custom_action : custom_action_array) {
- custom_actions.push_back(CustomActionConfig(custom_action));
- }
- }
- if (FLAGS_custom_actions != "") {
- // Load the custom action from the --config preset file.
- std::unique_ptr<Json::CharReader> reader(builder.newCharReader());
- std::string errorMessage;
- if (!reader->parse(&*FLAGS_custom_actions.begin(), &*FLAGS_custom_actions.end(),
- &custom_action_array, &errorMessage)) {
- LOG(FATAL) << "Could not read custom actions config flag: "
- << errorMessage;
- }
- for (const auto& custom_action : custom_action_array) {
- custom_actions.push_back(CustomActionConfig(custom_action));
- }
- }
- tmp_config_obj.set_custom_actions(custom_actions);
+ tmp_config_obj.set_enable_vehicle_hal_grpc_server(
+ FLAGS_enable_vehicle_hal_grpc_server);
tmp_config_obj.set_bootloader(FLAGS_bootloader);
@@ -716,12 +707,34 @@
tmp_config_obj.set_vhost_net(FLAGS_vhost_net);
+ tmp_config_obj.set_vhost_user_mac80211_hwsim(FLAGS_vhost_user_mac80211_hwsim);
+
+ if ((FLAGS_ap_rootfs_image.empty()) != (FLAGS_ap_kernel_image.empty())) {
+ LOG(FATAL) << "Either both ap_rootfs_image and ap_kernel_image should be "
+ "set or neither should be set.";
+ }
+
+ tmp_config_obj.set_ap_rootfs_image(FLAGS_ap_rootfs_image);
+ tmp_config_obj.set_ap_kernel_image(FLAGS_ap_kernel_image);
+
+ tmp_config_obj.set_wmediumd_config(FLAGS_wmediumd_config);
+
+ tmp_config_obj.set_rootcanal_hci_port(7300);
+ tmp_config_obj.set_rootcanal_link_port(7400);
+ tmp_config_obj.set_rootcanal_test_port(7500);
+ tmp_config_obj.set_rootcanal_config_file(
+ FLAGS_bluetooth_controller_properties_file);
+ tmp_config_obj.set_rootcanal_default_commands_file(
+ FLAGS_bluetooth_default_commands_file);
+
tmp_config_obj.set_record_screen(FLAGS_record_screen);
tmp_config_obj.set_enable_host_bluetooth(FLAGS_enable_host_bluetooth);
tmp_config_obj.set_protected_vm(FLAGS_protected_vm);
+ tmp_config_obj.set_userdata_format(FLAGS_userdata_format);
+
std::vector<int> num_instances;
for (int i = 0; i < FLAGS_num_instances; i++) {
num_instances.push_back(GetInstance() + i);
@@ -743,10 +756,7 @@
auto instance = tmp_config_obj.ForInstance(num);
auto const_instance =
- const_cast<const CuttlefishConfig&>(tmp_config_obj)
- .ForInstance(num);
- // Set this first so that calls to PerInstancePath below are correct
- instance.set_instance_dir(instance_dir + "." + std::to_string(num));
+ const_cast<const CuttlefishConfig&>(tmp_config_obj).ForInstance(num);
instance.set_use_allocd(FLAGS_use_allocd);
if (FLAGS_use_random_serial) {
instance.set_serial_number(
@@ -770,17 +780,19 @@
instance.set_uuid(FLAGS_uuid);
- instance.set_vnc_server_port(6444 + num - 1);
- instance.set_host_port(6520 + num - 1);
+ instance.set_modem_simulator_host_id(1000 + num); // Must be 4 digits
+ // the deprecated vnc was 6444 + num - 1, and qemu_vnc was vnc - 5900
+ instance.set_qemu_vnc_server_port(544 + num - 1);
+ instance.set_adb_host_port(6520 + num - 1);
instance.set_adb_ip_and_port("0.0.0.0:" + std::to_string(6520 + num - 1));
+ instance.set_confui_host_vsock_port(7700 + num - 1);
instance.set_tombstone_receiver_port(calc_vsock_port(6600));
- instance.set_vehicle_hal_server_port(9210 + num - 1);
+ instance.set_vehicle_hal_server_port(9300 + num - 1);
instance.set_audiocontrol_server_port(9410); /* OK to use the same port number across instances */
instance.set_config_server_port(calc_vsock_port(6800));
if (tmp_config_obj.gpu_mode() != kGpuModeDrmVirgl &&
tmp_config_obj.gpu_mode() != kGpuModeGfxStream) {
- instance.set_frames_server_port(calc_vsock_port(6900));
if (FLAGS_vm_manager == QemuManager::name()) {
instance.set_keyboard_server_port(calc_vsock_port(7000));
instance.set_touch_server_port(calc_vsock_port(7100));
@@ -793,39 +805,33 @@
instance.set_gnss_file_path(gnss_file_paths[num-1]);
}
- instance.set_rootcanal_hci_port(7300 + num - 1);
- instance.set_rootcanal_link_port(7400 + num - 1);
- instance.set_rootcanal_test_port(7500 + num - 1);
- instance.set_rootcanal_config_file(
- FLAGS_bluetooth_controller_properties_file);
- instance.set_rootcanal_default_commands_file(
- FLAGS_bluetooth_default_commands_file);
-
instance.set_camera_server_port(FLAGS_camera_server_port);
- instance.set_device_title(FLAGS_device_title);
if (FLAGS_protected_vm) {
instance.set_virtual_disk_paths(
{const_instance.PerInstancePath("os_composite.img")});
} else {
std::vector<std::string> virtual_disk_paths = {
- const_instance.PerInstancePath("overlay.img"),
const_instance.PerInstancePath("persistent_composite.img"),
};
+ if (FLAGS_vm_manager != Gem5Manager::name()) {
+ virtual_disk_paths.insert(virtual_disk_paths.begin(),
+ const_instance.PerInstancePath("overlay.img"));
+ } else {
+ // Gem5 already uses CoW wrappers around disk images
+ virtual_disk_paths.insert(virtual_disk_paths.begin(),
+ tmp_config_obj.os_composite_disk_path());
+ }
if (FLAGS_use_sdcard) {
virtual_disk_paths.push_back(const_instance.sdcard_path());
}
instance.set_virtual_disk_paths(virtual_disk_paths);
}
- std::array<unsigned char, 6> mac_address;
- mac_address[0] = 1 << 6; // locally administered
- // TODO(schuffelen): Randomize these and preserve the state.
- for (int i = 1; i < 5; i++) {
- mac_address[i] = i;
- }
- mac_address[5] = num;
- instance.set_wifi_mac_address(mac_address);
+ // We'd like to set mac prefix to be 5554, 5555, 5556, ... in normal cases.
+ // When --base_instance_num=3, this might be 5556, 5557, 5558, ... (skipping
+ // first two)
+ instance.set_wifi_mac_prefix(5554 + (num - 1));
instance.set_start_webrtc_signaling_server(false);
@@ -840,14 +846,45 @@
}
instance.set_webrtc_device_id(device_id);
}
- if (FLAGS_start_webrtc_sig_server && is_first_instance) {
+ if (!is_first_instance || !FLAGS_start_webrtc) {
+ // Only the first instance starts the signaling server or proxy
+ instance.set_start_webrtc_signaling_server(false);
+ instance.set_start_webrtc_sig_server_proxy(false);
+ } else {
auto port = 8443 + num - 1;
// Change the signaling server port for all instances
tmp_config_obj.set_sig_server_port(port);
- instance.set_start_webrtc_signaling_server(true);
- } else {
- instance.set_start_webrtc_signaling_server(false);
+ // Either the signaling server or the proxy is started, never both
+ instance.set_start_webrtc_signaling_server(FLAGS_start_webrtc_sig_server);
+ // The proxy is only started if the host operator is available
+ instance.set_start_webrtc_sig_server_proxy(
+ cuttlefish::FileIsSocket(HOST_OPERATOR_SOCKET_PATH) &&
+ !FLAGS_start_webrtc_sig_server);
}
+
+ // Start wmediumd process for the first instance if
+ // vhost_user_mac80211_hwsim is not specified.
+ const bool start_wmediumd =
+ FLAGS_vhost_user_mac80211_hwsim.empty() && is_first_instance;
+ if (start_wmediumd) {
+ // TODO(b/199020470) move this to the directory for shared resources
+ auto vhost_user_socket_path =
+ const_instance.PerInstanceInternalPath("vhost_user_mac80211");
+ auto wmediumd_api_socket_path =
+ const_instance.PerInstanceInternalPath("wmediumd_api_server");
+
+ tmp_config_obj.set_vhost_user_mac80211_hwsim(vhost_user_socket_path);
+ tmp_config_obj.set_wmediumd_api_server_socket(wmediumd_api_socket_path);
+ instance.set_start_wmediumd(true);
+ } else {
+ instance.set_start_wmediumd(false);
+ }
+
+ instance.set_start_rootcanal(is_first_instance);
+
+ instance.set_start_ap(!FLAGS_ap_rootfs_image.empty() &&
+ !FLAGS_ap_kernel_image.empty() && is_first_instance);
+
is_first_instance = false;
// instance.modem_simulator_ports := "" or "[port,]*port"
@@ -866,112 +903,61 @@
}
} // end of num_instances loop
+ std::vector<std::string> names;
+ for (const auto& instance : tmp_config_obj.Instances()) {
+ names.emplace_back(instance.instance_name());
+ }
+ tmp_config_obj.set_instance_names(names);
+
tmp_config_obj.set_enable_sandbox(FLAGS_enable_sandbox);
- // Audio is not available for VNC server
+ // Audio is not available for Arm64
SetCommandLineOptionWithMode(
"enable_audio",
- (FLAGS_start_vnc_server || (cuttlefish::HostArch() == cuttlefish::Arch::Arm64))
- ? "false"
- : "true",
+ (cuttlefish::HostArch() == cuttlefish::Arch::Arm64) ? "false" : "true",
SET_FLAGS_DEFAULT);
tmp_config_obj.set_enable_audio(FLAGS_enable_audio);
return tmp_config_obj;
}
-void SetDefaultFlagsFromConfigPreset() {
- std::string config_preset = FLAGS_config; // The name of the preset config.
- std::string config_file_path; // The path to the preset config JSON.
- std::set<std::string> allowed_config_presets;
- for (const std::string& file :
- DirectoryContents(DefaultHostArtifactsPath("etc/cvd_config"))) {
- std::string_view local_file(file);
- if (android::base::ConsumePrefix(&local_file, "cvd_config_") &&
- android::base::ConsumeSuffix(&local_file, ".json")) {
- allowed_config_presets.emplace(local_file);
- }
- }
-
- // If the user specifies a --config name, then use that config
- // preset option.
- std::string android_info_path = FLAGS_system_image_dir + "/android-info.txt";
- if (IsFlagSet("config")) {
- if (!allowed_config_presets.count(config_preset)) {
- LOG(FATAL) << "Invalid --config option '" << config_preset
- << "'. Valid options: "
- << android::base::Join(allowed_config_presets, ",");
- }
- } else if (FileExists(android_info_path)) {
- // Otherwise try to load the correct preset using android-info.txt.
- std::ifstream ifs(android_info_path);
- if (ifs.is_open()) {
- std::string android_info;
- ifs >> android_info;
- std::string_view local_android_info(android_info);
- if (android::base::ConsumePrefix(&local_android_info, "config=")) {
- config_preset = local_android_info;
- }
- if (!allowed_config_presets.count(config_preset)) {
- LOG(WARNING) << android_info_path
- << " contains invalid config preset: '"
- << local_android_info << "'. Defaulting to 'phone'.";
- config_preset = "phone";
- }
- }
- }
- LOG(INFO) << "Launching CVD using --config='" << config_preset << "'.";
-
- config_file_path = DefaultHostArtifactsPath("etc/cvd_config/cvd_config_" +
- config_preset + ".json");
- Json::Value config;
- Json::CharReaderBuilder builder;
- std::ifstream ifs(config_file_path);
- std::string errorMessage;
- if (!Json::parseFromStream(builder, ifs, &config, &errorMessage)) {
- LOG(FATAL) << "Could not read config file " << config_file_path << ": "
- << errorMessage;
- }
- for (const std::string& flag : config.getMemberNames()) {
- std::string value;
- if (flag == "custom_actions") {
- Json::StreamWriterBuilder factory;
- value = Json::writeString(factory, config[flag]);
- } else {
- value = config[flag].asString();
- }
- if (gflags::SetCommandLineOptionWithMode(flag.c_str(), value.c_str(),
- SET_FLAGS_DEFAULT)
- .empty()) {
- LOG(FATAL) << "Error setting flag '" << flag << "'.";
- }
- }
-}
-
-void SetDefaultFlagsForQemu() {
+void SetDefaultFlagsForQemu(Arch target_arch) {
// for now, we don't set non-default options for QEMU
- if (FLAGS_gpu_mode == kGpuModeGuestSwiftshader && NumStreamers() == 0) {
+ if (FLAGS_gpu_mode == kGpuModeGuestSwiftshader && !FLAGS_start_webrtc) {
// This makes WebRTC the default streamer unless the user requests
// another via a --star_<streamer> flag, while at the same time it's
// possible to run without any streamer by setting --start_webrtc=false.
SetCommandLineOptionWithMode("start_webrtc", "true", SET_FLAGS_DEFAULT);
}
- std::string default_bootloader = FLAGS_system_image_dir + "/bootloader.qemu";
+ std::string default_bootloader =
+ DefaultHostArtifactsPath("etc/bootloader_");
+ if(target_arch == Arch::Arm) {
+ // Bootloader is unstable >512MB RAM on 32-bit ARM
+ SetCommandLineOptionWithMode("memory_mb", "512", SET_FLAGS_VALUE);
+ default_bootloader += "arm";
+ } else if (target_arch == Arch::Arm64) {
+ default_bootloader += "aarch64";
+ } else {
+ default_bootloader += "x86_64";
+ }
+ default_bootloader += "/bootloader.qemu";
SetCommandLineOptionWithMode("bootloader", default_bootloader.c_str(),
SET_FLAGS_DEFAULT);
}
void SetDefaultFlagsForCrosvm() {
- if (NumStreamers() == 0) {
+ if (!FLAGS_start_webrtc) {
// This makes WebRTC the default streamer unless the user requests
// another via a --star_<streamer> flag, while at the same time it's
// possible to run without any streamer by setting --start_webrtc=false.
SetCommandLineOptionWithMode("start_webrtc", "true", SET_FLAGS_DEFAULT);
}
- // TODO(b/182484563): Re-enable autodetection when we fix the crosvm crashes
- bool default_enable_sandbox = false;
-
+ std::set<Arch> supported_archs{Arch::X86_64};
+ bool default_enable_sandbox =
+ supported_archs.find(HostArch()) != supported_archs.end() &&
+ EnsureDirectoryExists(kCrosvmVarEmptyDir).ok() &&
+ IsDirectoryEmpty(kCrosvmVarEmptyDir) && !IsRunningInContainer();
SetCommandLineOptionWithMode("enable_sandbox",
(default_enable_sandbox ? "true" : "false"),
SET_FLAGS_DEFAULT);
@@ -981,19 +967,21 @@
SET_FLAGS_DEFAULT);
}
-bool ParseCommandLineFlags(int* argc, char*** argv, KernelConfig* kernel_config) {
- google::ParseCommandLineNonHelpFlags(argc, argv, true);
- SetDefaultFlagsFromConfigPreset();
- google::HandleCommandLineHelpFlags();
- bool invalid_manager = false;
+void SetDefaultFlagsForGem5() {
+ // TODO: Add support for gem5 gpu models
+ SetCommandLineOptionWithMode("gpu_mode", kGpuModeGuestSwiftshader,
+ SET_FLAGS_DEFAULT);
- if (!ResolveInstanceFiles()) {
- return false;
- }
+ SetCommandLineOptionWithMode("cpus", "1", SET_FLAGS_DEFAULT);
+}
- ReadKernelConfig(kernel_config);
+Result<KernelConfig> GetKernelConfigAndSetDefaults() {
+ CF_EXPECT(ResolveInstanceFiles(), "Failed to resolve instance files");
+
+ KernelConfig kernel_config = CF_EXPECT(ReadKernelConfig());
+
if (FLAGS_vm_manager == "") {
- if (IsHostCompatible(kernel_config->target_arch)) {
+ if (IsHostCompatible(kernel_config.target_arch)) {
FLAGS_vm_manager = CrosvmManager::name();
} else {
FLAGS_vm_manager = QemuManager::name();
@@ -1001,26 +989,36 @@
}
if (FLAGS_vm_manager == QemuManager::name()) {
- SetDefaultFlagsForQemu();
+ SetDefaultFlagsForQemu(kernel_config.target_arch);
} else if (FLAGS_vm_manager == CrosvmManager::name()) {
SetDefaultFlagsForCrosvm();
+ } else if (FLAGS_vm_manager == Gem5Manager::name()) {
+ // TODO: Get the other architectures working
+ if (kernel_config.target_arch != Arch::Arm64) {
+ return CF_ERR("Gem5 only supports ARM64");
+ }
+ SetDefaultFlagsForGem5();
} else {
- std::cerr << "Unknown Virtual Machine Manager: " << FLAGS_vm_manager
- << std::endl;
- invalid_manager = true;
+ return CF_ERR("Unknown Virtual Machine Manager: " << FLAGS_vm_manager);
}
- // The default for starting signaling server is whether or not webrt is to be
- // started.
- SetCommandLineOptionWithMode("start_webrtc_sig_server",
- FLAGS_start_webrtc ? "true" : "false",
- SET_FLAGS_DEFAULT);
- if (invalid_manager) {
- return false;
+ if (FLAGS_vm_manager != Gem5Manager::name()) {
+ auto host_operator_present =
+ cuttlefish::FileIsSocket(HOST_OPERATOR_SOCKET_PATH);
+ // The default for starting signaling server depends on whether or not webrtc
+ // is to be started and the presence of the host orchestrator.
+ SetCommandLineOptionWithMode(
+ "start_webrtc_sig_server",
+ FLAGS_start_webrtc && !host_operator_present ? "true" : "false",
+ SET_FLAGS_DEFAULT);
+ SetCommandLineOptionWithMode(
+ "webrtc_sig_server_addr",
+ host_operator_present ? HOST_OPERATOR_SOCKET_PATH : "0.0.0.0",
+ SET_FLAGS_DEFAULT);
}
// Set the env variable to empty (in case the caller passed a value for it).
unsetenv(kCuttlefishConfigEnvVarName);
- return true;
+ return kernel_config;
}
std::string GetConfigFilePath(const CuttlefishConfig& config) {
diff --git a/host/commands/assemble_cvd/flags.h b/host/commands/assemble_cvd/flags.h
index 3c02d92..bd067ae 100644
--- a/host/commands/assemble_cvd/flags.h
+++ b/host/commands/assemble_cvd/flags.h
@@ -1,9 +1,13 @@
#pragma once
+#include <fruit/fruit.h>
#include <cstdint>
#include <optional>
+#include <string>
+#include <vector>
#include "common/libs/utils/environment.h"
+#include "common/libs/utils/result.h"
#include "host/libs/config/cuttlefish_config.h"
#include "host/libs/config/fetcher_config.h"
@@ -14,12 +18,12 @@
bool bootconfig_supported;
};
-bool ParseCommandLineFlags(int* argc, char*** argv,
- KernelConfig* kernel_config);
+Result<KernelConfig> GetKernelConfigAndSetDefaults();
// Must be called after ParseCommandLineFlags.
-CuttlefishConfig InitializeCuttlefishConfiguration(
- const std::string& instance_dir, int modem_simulator_count,
- KernelConfig kernel_config);
+CuttlefishConfig InitializeCuttlefishConfiguration(const std::string& root_dir,
+ int modem_simulator_count,
+ KernelConfig kernel_config,
+ fruit::Injector<>& injector);
std::string GetConfigFilePath(const CuttlefishConfig& config);
std::string GetCuttlefishEnvPath();
diff --git a/host/commands/assemble_cvd/super_image_mixer.cc b/host/commands/assemble_cvd/super_image_mixer.cc
index c1e7eac..f048a81 100644
--- a/host/commands/assemble_cvd/super_image_mixer.cc
+++ b/host/commands/assemble_cvd/super_image_mixer.cc
@@ -55,14 +55,10 @@
const std::string kMiscInfoPath = "META/misc_info.txt";
const std::set<std::string> kDefaultTargetImages = {
- "IMAGES/boot.img",
- "IMAGES/odm.img",
- "IMAGES/odm_dlkm.img",
- "IMAGES/recovery.img",
- "IMAGES/userdata.img",
- "IMAGES/vbmeta.img",
- "IMAGES/vendor.img",
- "IMAGES/vendor_dlkm.img",
+ "IMAGES/boot.img", "IMAGES/init_boot.img", "IMAGES/odm.img",
+ "IMAGES/odm_dlkm.img", "IMAGES/recovery.img", "IMAGES/userdata.img",
+ "IMAGES/vbmeta.img", "IMAGES/vendor.img", "IMAGES/vendor_dlkm.img",
+ "IMAGES/system_dlkm.img",
};
const std::set<std::string> kDefaultTargetBuildProp = {
"ODM/build.prop",
@@ -133,7 +129,8 @@
auto output_misc = default_misc;
auto system_super_partitions = SuperPartitionComponents(system_misc);
// Ensure specific skipped partitions end up in the misc_info.txt
- for (auto partition : {"odm", "odm_dlkm", "vendor", "vendor_dlkm"}) {
+ for (auto partition :
+ {"odm", "odm_dlkm", "vendor", "vendor_dlkm", "system_dlkm"}) {
if (std::find(system_super_partitions.begin(), system_super_partitions.end(),
partition) == system_super_partitions.end()) {
system_super_partitions.push_back(partition);
@@ -241,10 +238,7 @@
}) == 0;
}
-} // namespace
-
-bool SuperImageNeedsRebuilding(const FetcherConfig& fetcher_config,
- const CuttlefishConfig&) {
+bool SuperImageNeedsRebuilding(const FetcherConfig& fetcher_config) {
bool has_default_build = false;
bool has_system_build = false;
for (const auto& file_iter : fetcher_config.get_cvd_files()) {
@@ -288,4 +282,50 @@
return success;
}
+class SuperImageOutputPathTag {};
+
+class SuperImageRebuilderImpl : public SuperImageRebuilder {
+ public:
+ INJECT(SuperImageRebuilderImpl(const FetcherConfig& fetcher_config,
+ const CuttlefishConfig& config,
+ ANNOTATED(SuperImageOutputPathTag, std::string)
+ output_path))
+ : fetcher_config_(fetcher_config),
+ config_(config),
+ output_path_(output_path) {}
+
+ std::string Name() const override { return "SuperImageRebuilderImpl"; }
+ bool Enabled() const override { return true; }
+
+ private:
+ std::unordered_set<SetupFeature*> Dependencies() const override { return {}; }
+ bool Setup() override {
+ if (SuperImageNeedsRebuilding(fetcher_config_)) {
+ bool success = RebuildSuperImage(fetcher_config_, config_, output_path_);
+ if (!success) {
+ LOG(ERROR)
+ << "Super image rebuilding requested but could not be completed.";
+ return false;
+ }
+ }
+ return true;
+ }
+
+ const FetcherConfig& fetcher_config_;
+ const CuttlefishConfig& config_;
+ std::string output_path_;
+};
+
+} // namespace
+
+fruit::Component<fruit::Required<const FetcherConfig, const CuttlefishConfig>,
+ SuperImageRebuilder>
+SuperImageRebuilderComponent(const std::string* output_path) {
+ return fruit::createComponent()
+ .bindInstance<fruit::Annotated<SuperImageOutputPathTag, std::string>>(
+ *output_path)
+ .bind<SuperImageRebuilder, SuperImageRebuilderImpl>()
+ .addMultibinding<SetupFeature, SuperImageRebuilder>();
+}
+
} // namespace cuttlefish
diff --git a/host/commands/assemble_cvd/super_image_mixer.h b/host/commands/assemble_cvd/super_image_mixer.h
index 91f2c13..fda7f4d 100644
--- a/host/commands/assemble_cvd/super_image_mixer.h
+++ b/host/commands/assemble_cvd/super_image_mixer.h
@@ -13,15 +13,18 @@
// See the License for the specific language governing permissions and
// limitations under the License.
+#include <fruit/fruit.h>
+
#include "host/libs/config/cuttlefish_config.h"
+#include "host/libs/config/feature.h"
#include "host/libs/config/fetcher_config.h"
namespace cuttlefish {
-bool SuperImageNeedsRebuilding(const FetcherConfig& fetcher_config,
- const CuttlefishConfig& config);
-bool RebuildSuperImage(const FetcherConfig& fetcher_config,
- const CuttlefishConfig& config,
- const std::string& output_path);
+class SuperImageRebuilder : public SetupFeature {};
+
+fruit::Component<fruit::Required<const FetcherConfig, const CuttlefishConfig>,
+ SuperImageRebuilder>
+SuperImageRebuilderComponent(const std::string* output_path);
} // namespace cuttlefish
diff --git a/host/commands/bt_connector/Android.bp b/host/commands/bt_connector/Android.bp
index d617eaa..8703a76 100644
--- a/host/commands/bt_connector/Android.bp
+++ b/host/commands/bt_connector/Android.bp
@@ -24,6 +24,7 @@
"main.cpp",
],
shared_libs: [
+ "libext2_blkid",
"libbase",
"libcuttlefish_fs",
"libjsoncpp",
diff --git a/host/commands/bt_connector/OWNERS b/host/commands/bt_connector/OWNERS
index e8a4a00..e791d83 100644
--- a/host/commands/bt_connector/OWNERS
+++ b/host/commands/bt_connector/OWNERS
@@ -1,2 +1,3 @@
+include device/google/cuttlefish:/OWNERS
include platform/system/bt:/OWNERS
[email protected]
\ No newline at end of file
diff --git a/host/commands/bt_connector/main.cpp b/host/commands/bt_connector/main.cpp
index fc17b31..a625768 100644
--- a/host/commands/bt_connector/main.cpp
+++ b/host/commands/bt_connector/main.cpp
@@ -14,7 +14,7 @@
*/
#include <fcntl.h>
-#include <sys/poll.h>
+#include <poll.h>
#include <unistd.h>
#include <ios>
#include <mutex>
diff --git a/host/commands/config_server/Android.bp b/host/commands/config_server/Android.bp
index b567bd9..51f3015 100644
--- a/host/commands/config_server/Android.bp
+++ b/host/commands/config_server/Android.bp
@@ -23,6 +23,7 @@
"main.cpp",
],
shared_libs: [
+ "libext2_blkid",
"libbase",
"libcuttlefish_fs",
"libjsoncpp",
diff --git a/host/commands/console_forwarder/Android.bp b/host/commands/console_forwarder/Android.bp
index 230a0f7..c98c7aa 100644
--- a/host/commands/console_forwarder/Android.bp
+++ b/host/commands/console_forwarder/Android.bp
@@ -23,6 +23,7 @@
"main.cpp",
],
shared_libs: [
+ "libext2_blkid",
"libbase",
"libjsoncpp",
"libcuttlefish_fs",
diff --git a/host/commands/console_forwarder/main.cpp b/host/commands/console_forwarder/main.cpp
index df5c9f7..bf8b21a 100644
--- a/host/commands/console_forwarder/main.cpp
+++ b/host/commands/console_forwarder/main.cpp
@@ -52,11 +52,13 @@
class ConsoleForwarder {
public:
ConsoleForwarder(std::string console_path, SharedFD console_in,
- SharedFD console_out, SharedFD console_log)
+ SharedFD console_out, SharedFD console_log,
+ SharedFD kernel_log)
: console_path_(console_path),
console_in_(console_in),
console_out_(console_out),
- console_log_(console_log) {}
+ console_log_(console_log),
+ kernel_log_(kernel_log) {}
[[noreturn]] void StartServer() {
// Create a new thread to handle writes to the console
writer_thread_ = std::thread([this]() { WriteLoop(); });
@@ -176,6 +178,7 @@
if (client_fd->IsOpen()) {
EnqueueWrite(buf_ptr, client_fd);
}
+ EnqueueWrite(buf_ptr, kernel_log_);
}
if (read_set.IsSet(client_fd)) {
std::shared_ptr<std::vector<char>> buf_ptr = std::make_shared<std::vector<char>>(4096);
@@ -199,6 +202,7 @@
SharedFD console_in_;
SharedFD console_out_;
SharedFD console_log_;
+ SharedFD kernel_log_;
std::thread writer_thread_;
std::mutex write_queue_mutex_;
std::condition_variable condvar_;
@@ -232,7 +236,10 @@
auto console_log = instance.PerInstancePath("console_log");
auto console_log_fd =
SharedFD::Open(console_log.c_str(), O_CREAT | O_APPEND | O_WRONLY, 0666);
- ConsoleForwarder console_forwarder(console_path, console_in, console_out, console_log_fd);
+ auto kernel_log_fd = SharedFD::Open(instance.kernel_log_pipe_name(),
+ O_APPEND | O_WRONLY, 0666);
+ ConsoleForwarder console_forwarder(console_path, console_in, console_out,
+ console_log_fd, kernel_log_fd);
// Don't get a SIGPIPE from the clients
CHECK(sigaction(SIGPIPE, nullptr, nullptr) == 0)
diff --git a/host/commands/cvd/Android.bp b/host/commands/cvd/Android.bp
new file mode 100644
index 0000000..c79447b
--- /dev/null
+++ b/host/commands/cvd/Android.bp
@@ -0,0 +1,81 @@
+//
+// Copyright (C) 2021 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package {
+ default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+cc_binary_host {
+ name: "cvd",
+ symlinks: ["acloud"],
+ srcs: [
+ "acloud_command.cpp",
+ "command_sequence.cpp",
+ "epoll_loop.cpp",
+ "instance_lock.cpp",
+ "instance_manager.cpp",
+ "main.cc",
+ "scope_guard.cpp",
+ "server.cc",
+ "server_client.cpp",
+ "server_command.cpp",
+ "server_shutdown.cpp",
+ "server_version.cpp",
+ ],
+ target: {
+ host: {
+ stl: "libc++_static",
+ static_libs: [
+ "libbase",
+ "libcuttlefish_fs",
+ "libcuttlefish_utils",
+ "libext2_blkid",
+ "libfruit",
+ "libjsoncpp",
+ "liblog",
+ "libprotobuf-cpp-lite",
+ "libz",
+ ],
+ },
+ android: {
+ shared_libs: [
+ "libbase",
+ "libcuttlefish_fs",
+ "libcuttlefish_utils",
+ "libext2_blkid",
+ "libfruit",
+ "libjsoncpp",
+ "liblog",
+ "libprotobuf-cpp-lite",
+ "libz",
+ ],
+ },
+ },
+ static_libs: [
+ "libbuildversion",
+ "libcuttlefish_cvd_proto",
+ "libcuttlefish_host_config",
+ ],
+ required: [
+ "cvd_internal_host_bugreport",
+ "cvd_internal_start",
+ "cvd_internal_status",
+ "cvd_internal_stop",
+ ],
+ defaults: [
+ "cuttlefish_host",
+ ],
+ use_version_lib: true,
+}
diff --git a/host/commands/cvd/acloud_command.cpp b/host/commands/cvd/acloud_command.cpp
new file mode 100644
index 0000000..cc53d54
--- /dev/null
+++ b/host/commands/cvd/acloud_command.cpp
@@ -0,0 +1,335 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#include "host/commands/cvd/server.h"
+
+#include <optional>
+#include <vector>
+
+#include <android-base/strings.h>
+
+#include "cvd_server.pb.h"
+
+#include "common/libs/fs/shared_buf.h"
+#include "common/libs/fs/shared_fd.h"
+#include "common/libs/utils/flag_parser.h"
+#include "common/libs/utils/result.h"
+#include "common/libs/utils/subprocess.h"
+#include "host/commands/cvd/command_sequence.h"
+#include "host/commands/cvd/instance_lock.h"
+#include "host/commands/cvd/server_client.h"
+
+namespace cuttlefish {
+
+namespace {
+
+struct ConvertedAcloudCreateCommand {
+ InstanceLockFile lock;
+ std::vector<RequestWithStdio> requests;
+};
+
+/**
+ * Split a string into arguments based on shell tokenization rules.
+ *
+ * This behaves like `shlex.split` from python where arguments are separated
+ * based on whitespace, but quoting and quote escaping is respected. This
+ * function effectively removes one level of quoting from its inputs while
+ * making the split.
+ */
+Result<std::vector<std::string>> BashTokenize(const std::string& str) {
+ Command command("bash");
+ command.AddParameter("-c");
+ command.AddParameter("printf '%s\n' ", str);
+ std::string stdout;
+ std::string stderr;
+ auto ret = RunWithManagedStdio(std::move(command), nullptr, &stdout, &stderr);
+ CF_EXPECT(ret == 0, "printf fail \"" << stdout << "\", \"" << stderr << "\"");
+ return android::base::Split(stdout, "\n");
+}
+
+class ConvertAcloudCreateCommand {
+ public:
+ INJECT(ConvertAcloudCreateCommand(InstanceLockFileManager& lock_file_manager))
+ : lock_file_manager_(lock_file_manager) {}
+
+ Result<ConvertedAcloudCreateCommand> Convert(
+ const RequestWithStdio& request) {
+ auto arguments = ParseInvocation(request.Message()).arguments;
+ CF_EXPECT(arguments.size() > 0);
+ CF_EXPECT(arguments[0] == "create");
+ arguments.erase(arguments.begin());
+
+ const auto& request_command = request.Message().command_request();
+
+ std::vector<Flag> flags;
+ bool local_instance_set;
+ std::optional<int> local_instance;
+ auto local_instance_flag = Flag();
+ local_instance_flag.Alias(
+ {FlagAliasMode::kFlagConsumesArbitrary, "--local-instance"});
+ local_instance_flag.Setter([&local_instance_set,
+ &local_instance](const FlagMatch& m) {
+ local_instance_set = true;
+ if (m.value != "" && local_instance) {
+ LOG(ERROR) << "Instance number already set, was \"" << *local_instance
+ << "\", now set to \"" << m.value << "\"";
+ return false;
+ } else if (m.value != "" && !local_instance) {
+ local_instance = std::stoi(m.value);
+ }
+ return true;
+ });
+ flags.emplace_back(local_instance_flag);
+
+ bool verbose = false;
+ flags.emplace_back(Flag()
+ .Alias({FlagAliasMode::kFlagExact, "-v"})
+ .Alias({FlagAliasMode::kFlagExact, "-vv"})
+ .Alias({FlagAliasMode::kFlagExact, "--verbose"})
+ .Setter([&verbose](const FlagMatch&) {
+ verbose = true;
+ return true;
+ }));
+
+ std::optional<std::string> branch;
+ flags.emplace_back(
+ Flag()
+ .Alias({FlagAliasMode::kFlagConsumesFollowing, "--branch"})
+ .Setter([&branch](const FlagMatch& m) {
+ branch = m.value;
+ return true;
+ }));
+
+ bool local_image;
+ flags.emplace_back(
+ Flag()
+ .Alias({FlagAliasMode::kFlagConsumesArbitrary, "--local-image"})
+ .Setter([&local_image](const FlagMatch& m) {
+ local_image = true;
+ return m.value == "";
+ }));
+
+ std::optional<std::string> build_id;
+ flags.emplace_back(
+ Flag()
+ .Alias({FlagAliasMode::kFlagConsumesFollowing, "--build-id"})
+ .Alias({FlagAliasMode::kFlagConsumesFollowing, "--build_id"})
+ .Setter([&build_id](const FlagMatch& m) {
+ build_id = m.value;
+ return true;
+ }));
+
+ std::optional<std::string> build_target;
+ flags.emplace_back(
+ Flag()
+ .Alias({FlagAliasMode::kFlagConsumesFollowing, "--build-target"})
+ .Alias({FlagAliasMode::kFlagConsumesFollowing, "--build_target"})
+ .Setter([&build_target](const FlagMatch& m) {
+ build_target = m.value;
+ return true;
+ }));
+
+ std::optional<std::string> launch_args;
+ flags.emplace_back(
+ Flag()
+ .Alias({FlagAliasMode::kFlagConsumesFollowing, "--launch-args"})
+ .Setter([&launch_args](const FlagMatch& m) {
+ launch_args = m.value;
+ return true;
+ }));
+
+ CF_EXPECT(ParseFlags(flags, arguments));
+ CF_EXPECT(arguments.size() == 0,
+ "Unrecognized arguments:'"
+ << android::base::Join(arguments, "', '") << "'");
+
+ CF_EXPECT(local_instance_set == true,
+ "Only '--local-instance' is supported");
+ std::optional<InstanceLockFile> lock;
+ if (local_instance.has_value()) {
+ // TODO(schuffelen): Block here if it can be interruptible
+ lock = CF_EXPECT(lock_file_manager_.TryAcquireLock(*local_instance));
+ } else {
+ lock = CF_EXPECT(lock_file_manager_.TryAcquireUnusedLock());
+ }
+ CF_EXPECT(lock.has_value(), "Could not acquire instance lock");
+ CF_EXPECT(CF_EXPECT(lock->Status()) == InUseState::kNotInUse);
+
+ auto dir = TempDir() + "/acloud_cvd_temp/local-instance-" +
+ std::to_string(lock->Instance());
+
+ static constexpr char kAndroidHostOut[] = "ANDROID_HOST_OUT";
+
+ auto host_artifacts_path = request_command.env().find(kAndroidHostOut);
+ CF_EXPECT(host_artifacts_path != request_command.env().end(),
+ "Missing " << kAndroidHostOut);
+
+ std::vector<cvd::Request> request_protos;
+ if (local_image) {
+ cvd::Request& mkdir_request = request_protos.emplace_back();
+ auto& mkdir_command = *mkdir_request.mutable_command_request();
+ mkdir_command.add_args("cvd");
+ mkdir_command.add_args("mkdir");
+ mkdir_command.add_args("-p");
+ mkdir_command.add_args(dir);
+ auto& mkdir_env = *mkdir_command.mutable_env();
+ mkdir_env[kAndroidHostOut] = host_artifacts_path->second;
+ *mkdir_command.mutable_working_directory() = dir;
+ } else {
+ cvd::Request& fetch_request = request_protos.emplace_back();
+ auto& fetch_command = *fetch_request.mutable_command_request();
+ fetch_command.add_args("cvd");
+ fetch_command.add_args("fetch");
+ fetch_command.add_args("--directory");
+ fetch_command.add_args(dir);
+ if (branch || build_id || build_target) {
+ fetch_command.add_args("--default_build");
+ auto target = build_target ? "/" + *build_target : "";
+ auto build = build_id.value_or(branch.value_or("aosp-master"));
+ fetch_command.add_args(build + target);
+ }
+ *fetch_command.mutable_working_directory() = dir;
+ auto& fetch_env = *fetch_command.mutable_env();
+ fetch_env[kAndroidHostOut] = host_artifacts_path->second;
+ }
+
+ cvd::Request& start_request = request_protos.emplace_back();
+ auto& start_command = *start_request.mutable_command_request();
+ start_command.add_args("cvd");
+ start_command.add_args("start");
+ start_command.add_args("--daemon");
+ start_command.add_args("--undefok");
+ start_command.add_args("report_anonymous_usage_stats");
+ start_command.add_args("--report_anonymous_usage_stats");
+ start_command.add_args("y");
+ if (launch_args) {
+ for (const auto& arg : CF_EXPECT(BashTokenize(*launch_args))) {
+ start_command.add_args(arg);
+ }
+ }
+ static constexpr char kAndroidProductOut[] = "ANDROID_PRODUCT_OUT";
+ auto& start_env = *start_command.mutable_env();
+ if (local_image) {
+ start_env[kAndroidHostOut] = host_artifacts_path->second;
+
+ auto product_out = request_command.env().find(kAndroidProductOut);
+ CF_EXPECT(product_out != request_command.env().end(),
+ "Missing " << kAndroidProductOut);
+ start_env[kAndroidProductOut] = product_out->second;
+ } else {
+ start_env[kAndroidHostOut] = dir;
+ start_env[kAndroidProductOut] = dir;
+ }
+ start_env["CUTTLEFISH_INSTANCE"] = std::to_string(lock->Instance());
+ start_env["HOME"] = dir;
+ *start_command.mutable_working_directory() = dir;
+
+ std::vector<SharedFD> fds;
+ if (verbose) {
+ fds = request.FileDescriptors();
+ } else {
+ auto dev_null = SharedFD::Open("/dev/null", O_RDWR);
+ CF_EXPECT(dev_null->IsOpen(), dev_null->StrError());
+ fds = {dev_null, dev_null, dev_null};
+ }
+
+ ConvertedAcloudCreateCommand ret = {
+ .lock = {std::move(*lock)},
+ };
+ for (auto& request_proto : request_protos) {
+ ret.requests.emplace_back(request_proto, fds, request.Credentials());
+ }
+ return ret;
+ }
+
+ private:
+ InstanceLockFileManager& lock_file_manager_;
+};
+
+class TryAcloudCreateCommand : public CvdServerHandler {
+ public:
+ INJECT(TryAcloudCreateCommand(ConvertAcloudCreateCommand& converter))
+ : converter_(converter) {}
+ ~TryAcloudCreateCommand() = default;
+
+ Result<bool> CanHandle(const RequestWithStdio& request) const override {
+ auto invocation = ParseInvocation(request.Message());
+ return invocation.command == "try-acloud" &&
+ invocation.arguments.size() >= 1 &&
+ invocation.arguments[0] == "create";
+ }
+ Result<cvd::Response> Handle(const RequestWithStdio& request) override {
+ CF_EXPECT(converter_.Convert(request));
+ return CF_ERR("Unreleased");
+ }
+ Result<void> Interrupt() override { return CF_ERR("Can't be interrupted."); }
+
+ private:
+ ConvertAcloudCreateCommand& converter_;
+};
+
+class AcloudCreateCommand : public CvdServerHandler {
+ public:
+ INJECT(AcloudCreateCommand(CommandSequenceExecutor& executor,
+ ConvertAcloudCreateCommand& converter))
+ : executor_(executor), converter_(converter) {}
+ ~AcloudCreateCommand() = default;
+
+ Result<bool> CanHandle(const RequestWithStdio& request) const override {
+ auto invocation = ParseInvocation(request.Message());
+ return invocation.command == "acloud" && invocation.arguments.size() >= 1 &&
+ invocation.arguments[0] == "create";
+ }
+ Result<cvd::Response> Handle(const RequestWithStdio& request) override {
+ std::unique_lock interrupt_lock(interrupt_mutex_);
+ if (interrupted_) {
+ return CF_ERR("Interrupted");
+ }
+ CF_EXPECT(CanHandle(request));
+
+ auto converted = CF_EXPECT(converter_.Convert(request));
+ interrupt_lock.unlock();
+ CF_EXPECT(executor_.Execute(converted.requests, request.Err()));
+
+ CF_EXPECT(converted.lock.Status(InUseState::kInUse));
+
+ cvd::Response response;
+ response.mutable_command_response();
+ return response;
+ }
+ Result<void> Interrupt() override {
+ std::scoped_lock interrupt_lock(interrupt_mutex_);
+ interrupted_ = true;
+ CF_EXPECT(executor_.Interrupt());
+ return {};
+ }
+
+ private:
+ CommandSequenceExecutor& executor_;
+ ConvertAcloudCreateCommand& converter_;
+
+ std::mutex interrupt_mutex_;
+ bool interrupted_ = false;
+};
+
+} // namespace
+
+fruit::Component<fruit::Required<CvdCommandHandler>> AcloudCommandComponent() {
+ return fruit::createComponent()
+ .addMultibinding<CvdServerHandler, AcloudCreateCommand>()
+ .addMultibinding<CvdServerHandler, TryAcloudCreateCommand>();
+}
+
+} // namespace cuttlefish
diff --git a/host/commands/cvd/command_sequence.cpp b/host/commands/cvd/command_sequence.cpp
new file mode 100644
index 0000000..21b146c
--- /dev/null
+++ b/host/commands/cvd/command_sequence.cpp
@@ -0,0 +1,102 @@
+//
+// Copyright (C) 2022 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "host/commands/cvd/command_sequence.h"
+
+#include <fruit/fruit.h>
+
+#include "common/libs/fs/shared_buf.h"
+#include "host/commands/cvd/server.h"
+#include "host/commands/cvd/server_client.h"
+
+namespace cuttlefish {
+namespace {
+
+std::string BashEscape(const std::string& input) {
+ bool safe = true;
+ for (const auto& c : input) {
+ if ('0' <= c && c <= '9') {
+ continue;
+ }
+ if ('a' <= c && c <= 'z') {
+ continue;
+ }
+ if ('A' <= c && c <= 'Z') {
+ continue;
+ }
+ if (c == '_' || c == '-' || c == '.' || c == ',' || c == '/') {
+ continue;
+ }
+ safe = false;
+ }
+ using android::base::StringReplace;
+ return safe ? input : "'" + StringReplace(input, "'", "\\'", true) + "'";
+}
+
+std::string FormattedCommand(const cvd::CommandRequest command) {
+ std::stringstream effective_command;
+ effective_command << "Executing `";
+ for (const auto& [name, val] : command.env()) {
+ effective_command << BashEscape(name) << "=" << BashEscape(val) << " ";
+ }
+ for (const auto& argument : command.args()) {
+ effective_command << BashEscape(argument) << " ";
+ }
+ effective_command.seekp(-1, effective_command.cur);
+ effective_command << "`\n"; // Overwrite last space
+ return effective_command.str();
+}
+
+} // namespace
+
+CommandSequenceExecutor::CommandSequenceExecutor(
+ CvdCommandHandler& inner_handler)
+ : inner_handler_(inner_handler) {}
+
+Result<void> CommandSequenceExecutor::Interrupt() {
+ CF_EXPECT(inner_handler_.Interrupt());
+ return {};
+}
+
+Result<void> CommandSequenceExecutor::Execute(
+ const std::vector<RequestWithStdio>& requests, SharedFD report) {
+ std::unique_lock interrupt_lock(interrupt_mutex_);
+ if (interrupted_) {
+ return CF_ERR("Interrupted");
+ }
+ for (const auto& request : requests) {
+ auto& inner_proto = request.Message();
+ CF_EXPECT(inner_proto.has_command_request());
+ auto& command = inner_proto.command_request();
+ std::string str = FormattedCommand(command);
+ CF_EXPECT(WriteAll(report, str) == str.size(), report->StrError());
+
+ interrupt_lock.unlock();
+ auto response = CF_EXPECT(inner_handler_.Handle(request));
+ interrupt_lock.lock();
+ if (interrupted_) {
+ return CF_ERR("Interrupted");
+ }
+ CF_EXPECT(response.status().code() == cvd::Status::OK,
+ "Reason: \"" << response.status().message() << "\"");
+
+ static const char kDoneMsg[] = "Done\n";
+ CF_EXPECT(WriteAll(request.Err(), kDoneMsg) == sizeof(kDoneMsg) - 1,
+ request.Err()->StrError());
+ }
+ return {};
+}
+
+} // namespace cuttlefish
diff --git a/host/commands/cvd/command_sequence.h b/host/commands/cvd/command_sequence.h
new file mode 100644
index 0000000..bea305b
--- /dev/null
+++ b/host/commands/cvd/command_sequence.h
@@ -0,0 +1,41 @@
+//
+// Copyright (C) 2022 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#pragma once
+
+#include <vector>
+
+#include <fruit/fruit.h>
+
+#include "common/libs/fs/shared_fd.h"
+#include "host/commands/cvd/server.h"
+#include "host/commands/cvd/server_client.h"
+
+namespace cuttlefish {
+
+class CommandSequenceExecutor {
+ public:
+ INJECT(CommandSequenceExecutor(CvdCommandHandler& inner_handler));
+
+ Result<void> Interrupt();
+ Result<void> Execute(const std::vector<RequestWithStdio>&, SharedFD report);
+
+ private:
+ std::mutex interrupt_mutex_;
+ bool interrupted_ = false;
+ CvdCommandHandler& inner_handler_;
+};
+
+} // namespace cuttlefish
diff --git a/host/commands/cvd/epoll_loop.cpp b/host/commands/cvd/epoll_loop.cpp
new file mode 100644
index 0000000..51a6f6a
--- /dev/null
+++ b/host/commands/cvd/epoll_loop.cpp
@@ -0,0 +1,97 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "host/commands/cvd/epoll_loop.h"
+
+#include <android-base/errors.h>
+
+#include "common/libs/fs/epoll.h"
+#include "common/libs/fs/shared_fd.h"
+#include "common/libs/utils/result.h"
+
+namespace cuttlefish {
+
+EpollPool::EpollPool(Epoll epoll) : epoll_(std::move(epoll)) {}
+
+EpollPool::EpollPool(EpollPool&& other) {
+ std::unique_lock own_lock(instance_mutex_, std::defer_lock);
+ std::unique_lock other_lock(other.instance_mutex_, std::defer_lock);
+ std::unique_lock own_cb_lock(callbacks_mutex_, std::defer_lock);
+ std::unique_lock other_cb_lock(other.callbacks_mutex_, std::defer_lock);
+ std::lock(own_lock, other_lock, own_cb_lock, other_cb_lock);
+ epoll_ = std::move(other.epoll_);
+ callbacks_ = std::move(other.callbacks_);
+}
+
+EpollPool& EpollPool::operator=(EpollPool&& other) {
+ std::unique_lock own_lock(instance_mutex_, std::defer_lock);
+ std::unique_lock other_lock(other.instance_mutex_, std::defer_lock);
+ std::unique_lock own_cb_lock(callbacks_mutex_, std::defer_lock);
+ std::unique_lock other_cb_lock(other.callbacks_mutex_, std::defer_lock);
+ std::lock(own_lock, other_lock, own_cb_lock, other_cb_lock);
+ epoll_ = std::move(other.epoll_);
+ callbacks_ = std::move(other.callbacks_);
+
+ return *this;
+}
+
+Result<void> EpollPool::Register(SharedFD fd, uint32_t events,
+ EpollCallback callback) {
+ std::shared_lock instance_lock(instance_mutex_, std::defer_lock);
+ std::unique_lock callbacks_lock(callbacks_mutex_, std::defer_lock);
+ std::lock(instance_lock, callbacks_lock);
+ if (callbacks_.find(fd) != callbacks_.end()) {
+ return CF_ERR("Already have a callback created");
+ }
+ CF_EXPECT(epoll_.AddOrModify(fd, events | EPOLLONESHOT));
+ callbacks_[fd] = std::move(callback);
+ return {};
+}
+
+Result<void> EpollPool::HandleEvent() {
+ auto event = CF_EXPECT(epoll_.Wait());
+ if (!event) {
+ return {};
+ }
+ EpollCallback callback;
+ {
+ std::lock_guard lock(callbacks_mutex_);
+ auto it = callbacks_.find(event->fd);
+ CF_EXPECT(it != callbacks_.end(), "Could not find event callback");
+ callback = std::move(it->second);
+ callbacks_.erase(it);
+ }
+ CF_EXPECT(callback(*event));
+ return {};
+}
+
+Result<void> EpollPool::Remove(SharedFD fd) {
+ std::shared_lock instance_lock(instance_mutex_, std::defer_lock);
+ std::unique_lock callbacks_lock(callbacks_mutex_, std::defer_lock);
+ std::lock(instance_lock, callbacks_lock);
+ CF_EXPECT(epoll_.Delete(fd), "No callback registered with epoll");
+ callbacks_.erase(fd);
+ return {};
+}
+
+fruit::Component<EpollPool> EpollLoopComponent() {
+ return fruit::createComponent()
+ .registerProvider([]() -> EpollPool {
+ return EpollPool(OR_FATAL(Epoll::Create()));
+ });
+}
+
+} // namespace cuttlefish
diff --git a/host/commands/cvd/epoll_loop.h b/host/commands/cvd/epoll_loop.h
new file mode 100644
index 0000000..8ae48d4
--- /dev/null
+++ b/host/commands/cvd/epoll_loop.h
@@ -0,0 +1,62 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <functional>
+#include <map>
+#include <mutex>
+
+#include <fruit/fruit.h>
+
+#include "common/libs/fs/epoll.h"
+#include "common/libs/fs/shared_fd.h"
+#include "common/libs/utils/result.h"
+
+#pragma once
+
+namespace cuttlefish {
+
+using EpollCallback = std::function<Result<void>(EpollEvent)>;
+
+class EpollPool {
+ public:
+ EpollPool(Epoll);
+ EpollPool(EpollPool&&);
+ EpollPool& operator=(EpollPool&&);
+
+ /**
+ * The `callback` function will be invoked with an EpollEvent containing `fd`
+ * and a subset of the bits in `events` matching which events were actually
+ * observed. The callback is invoked exactly once (enforced via EPOLLONESHOT)
+ * and must be re-`Register`ed to receive events again. This can be done
+ * in the callback implementation. Callbacks are invoked by callers of the
+ * `HandleEvent` function, and any errors produced by the callback function
+ * will manifest there. Callbacks that return errors will not be automatically
+ * re-registered.
+ */
+ Result<void> Register(SharedFD fd, uint32_t events, EpollCallback callback);
+ Result<void> HandleEvent();
+ Result<void> Remove(SharedFD fd);
+
+ private:
+ std::shared_mutex instance_mutex_;
+ Epoll epoll_;
+ std::mutex callbacks_mutex_;
+ std::map<SharedFD, EpollCallback> callbacks_;
+};
+
+fruit::Component<EpollPool> EpollLoopComponent();
+
+} // namespace cuttlefish
diff --git a/host/commands/cvd/instance_lock.cpp b/host/commands/cvd/instance_lock.cpp
new file mode 100644
index 0000000..de8b7b5
--- /dev/null
+++ b/host/commands/cvd/instance_lock.cpp
@@ -0,0 +1,188 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "host/commands/cvd/instance_lock.h"
+
+#include <sys/file.h>
+
+#include <algorithm>
+#include <sstream>
+#include <string>
+
+#include <android-base/file.h>
+#include <android-base/strings.h>
+#include <fruit/fruit.h>
+
+#include "common/libs/fs/shared_fd.h"
+#include "common/libs/utils/environment.h"
+#include "common/libs/utils/files.h"
+#include "common/libs/utils/result.h"
+
+namespace cuttlefish {
+
+InstanceLockFile::InstanceLockFile(SharedFD fd, int instance_num)
+ : fd_(fd), instance_num_(instance_num) {}
+
+int InstanceLockFile::Instance() const { return instance_num_; }
+
+Result<InUseState> InstanceLockFile::Status() const {
+ CF_EXPECT(fd_->LSeek(0, SEEK_SET) == 0, fd_->StrError());
+ char state_char = static_cast<char>(InUseState::kNotInUse);
+ CF_EXPECT(fd_->Read(&state_char, 1) >= 0, fd_->StrError());
+ switch (state_char) {
+ case static_cast<char>(InUseState::kInUse):
+ return InUseState::kInUse;
+ case static_cast<char>(InUseState::kNotInUse):
+ return InUseState::kNotInUse;
+ default:
+ return CF_ERR("Unexpected state value \"" << state_char << "\"");
+ }
+}
+
+Result<void> InstanceLockFile::Status(InUseState state) {
+ CF_EXPECT(fd_->LSeek(0, SEEK_SET) == 0, fd_->StrError());
+ char state_char = static_cast<char>(state);
+ CF_EXPECT(fd_->Write(&state_char, 1) == 1, fd_->StrError());
+ return {};
+}
+
+bool InstanceLockFile::operator<(const InstanceLockFile& other) const {
+ if (instance_num_ != other.instance_num_) {
+ return instance_num_ < other.instance_num_;
+ }
+ return fd_ < other.fd_;
+}
+
+InstanceLockFileManager::InstanceLockFileManager() = default;
+
+// Replicates tempfile.gettempdir() in Python
+std::string TempDir() {
+ std::vector<std::string> try_dirs = {
+ StringFromEnv("TMPDIR", ""),
+ StringFromEnv("TEMP", ""),
+ StringFromEnv("TMP", ""),
+ "/tmp",
+ "/var/tmp",
+ "/usr/tmp",
+ };
+ for (const auto& try_dir : try_dirs) {
+ if (DirectoryExists(try_dir)) {
+ return try_dir;
+ }
+ }
+ return CurrentDirectory();
+}
+
+static Result<SharedFD> OpenLockFile(int instance_num) {
+ std::stringstream path;
+ path << TempDir() << "/acloud_cvd_temp/";
+ CF_EXPECT(EnsureDirectoryExists(path.str()));
+ path << "local-instance-" << instance_num << ".lock";
+ auto fd = SharedFD::Open(path.str(), O_CREAT | O_RDWR, 0666);
+ CF_EXPECT(fd->IsOpen(), "open(\"" << path.str() << "\"): " << fd->StrError());
+ return fd;
+}
+
+Result<InstanceLockFile> InstanceLockFileManager::AcquireLock(
+ int instance_num) {
+ auto fd = CF_EXPECT(OpenLockFile(instance_num));
+ CF_EXPECT(fd->Flock(LOCK_EX), fd->StrError());
+ return InstanceLockFile(fd, instance_num);
+}
+
+Result<std::set<InstanceLockFile>> InstanceLockFileManager::AcquireLocks(
+ const std::set<int>& instance_nums) {
+ std::set<InstanceLockFile> locks;
+ for (const auto& num : instance_nums) {
+ locks.emplace(CF_EXPECT(AcquireLock(num)));
+ }
+ return locks;
+}
+
+Result<std::optional<InstanceLockFile>> InstanceLockFileManager::TryAcquireLock(
+ int instance_num) {
+ auto fd = CF_EXPECT(OpenLockFile(instance_num));
+ int flock_result = fd->Flock(LOCK_EX | LOCK_NB);
+ if (flock_result == 0) {
+ return InstanceLockFile(fd, instance_num);
+ } else if (flock_result == -1 && fd->GetErrno() == EWOULDBLOCK) {
+ return {};
+ }
+ return CF_ERR("flock " << instance_num << " failed: " << fd->StrError());
+}
+
+Result<std::set<InstanceLockFile>> InstanceLockFileManager::TryAcquireLocks(
+ const std::set<int>& instance_nums) {
+ std::set<InstanceLockFile> locks;
+ for (const auto& num : instance_nums) {
+ auto lock = CF_EXPECT(TryAcquireLock(num));
+ if (lock) {
+ locks.emplace(std::move(*lock));
+ }
+ }
+ return locks;
+}
+
+static Result<std::set<int>> AllInstanceNums() {
+ // Estimate this by looking at available tap devices
+ // clang-format off
+ /** Sample format:
+Inter-| Receive | Transmit
+ face |bytes packets errs drop fifo frame compressed multicast|bytes packets errs drop fifo colls carrier compressed
+cvd-wtap-02: 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+ */
+ // clang-format on
+ static constexpr char kPath[] = "/proc/net/dev";
+ std::string proc_net_dev;
+ using android::base::ReadFileToString;
+ CF_EXPECT(ReadFileToString(kPath, &proc_net_dev, /* follow_symlinks */ true));
+ auto lines = android::base::Split(proc_net_dev, "\n");
+ std::set<int> etaps, mtaps, wtaps;
+ for (const auto& line : lines) {
+ std::set<int>* tap_set = nullptr;
+ if (android::base::StartsWith(line, "cvd-etap-")) {
+ tap_set = &etaps;
+ } else if (android::base::StartsWith(line, "cvd-mtap-")) {
+ tap_set = &mtaps;
+ } else if (android::base::StartsWith(line, "cvd-wtap-")) {
+ tap_set = &wtaps;
+ } else {
+ continue;
+ }
+ tap_set->insert(std::stoi(line.substr(std::string{"cvd-etap-"}.size())));
+ }
+ std::set<int> emtaps;
+ std::set_intersection(etaps.begin(), etaps.end(), mtaps.begin(), mtaps.end(),
+ std::inserter(emtaps, emtaps.begin()));
+ std::set<int> emwtaps;
+ std::set_intersection(emtaps.begin(), emtaps.end(), wtaps.begin(),
+ wtaps.end(), std::inserter(emwtaps, emwtaps.begin()));
+ return emwtaps;
+}
+
+Result<std::optional<InstanceLockFile>>
+InstanceLockFileManager::TryAcquireUnusedLock() {
+ auto nums = CF_EXPECT(AllInstanceNums());
+ for (const auto& num : nums) {
+ auto lock = CF_EXPECT(TryAcquireLock(num));
+ if (lock && CF_EXPECT(lock->Status()) == InUseState::kNotInUse) {
+ return std::move(*lock);
+ }
+ }
+ return {};
+}
+
+} // namespace cuttlefish
diff --git a/host/commands/cvd/instance_lock.h b/host/commands/cvd/instance_lock.h
new file mode 100644
index 0000000..5d3deea
--- /dev/null
+++ b/host/commands/cvd/instance_lock.h
@@ -0,0 +1,69 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#pragma once
+
+#include <set>
+
+#include <fruit/fruit.h>
+
+#include "common/libs/fs/shared_fd.h"
+#include "common/libs/utils/result.h"
+
+namespace cuttlefish {
+
+class InstanceLockFileManager;
+
+enum class InUseState : char {
+ kInUse = 'I',
+ kNotInUse = 'N',
+};
+
+// Replicates tempfile.gettempdir() in Python
+std::string TempDir();
+
+// This class is not thread safe.
+class InstanceLockFile {
+ public:
+ int Instance() const;
+ Result<InUseState> Status() const;
+ Result<void> Status(InUseState);
+
+ bool operator<(const InstanceLockFile&) const;
+
+ private:
+ friend class InstanceLockFileManager;
+
+ InstanceLockFile(SharedFD fd, int instance_num);
+
+ SharedFD fd_;
+ int instance_num_;
+};
+
+class InstanceLockFileManager {
+ public:
+ INJECT(InstanceLockFileManager());
+
+ Result<InstanceLockFile> AcquireLock(int instance_num);
+ Result<std::set<InstanceLockFile>> AcquireLocks(const std::set<int>& nums);
+
+ Result<std::optional<InstanceLockFile>> TryAcquireLock(int instance_num);
+ Result<std::set<InstanceLockFile>> TryAcquireLocks(const std::set<int>& nums);
+
+ // Best-effort attempt to find a free instance id.
+ Result<std::optional<InstanceLockFile>> TryAcquireUnusedLock();
+};
+
+} // namespace cuttlefish
diff --git a/host/commands/cvd/instance_manager.cpp b/host/commands/cvd/instance_manager.cpp
new file mode 100644
index 0000000..7ea1b74
--- /dev/null
+++ b/host/commands/cvd/instance_manager.cpp
@@ -0,0 +1,163 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "host/commands/cvd/instance_manager.h"
+
+#include <map>
+#include <mutex>
+#include <optional>
+#include <thread>
+
+#include <android-base/file.h>
+#include <android-base/logging.h>
+#include <fruit/fruit.h>
+
+#include "cvd_server.pb.h"
+
+#include "common/libs/fs/shared_buf.h"
+#include "common/libs/fs/shared_fd.h"
+#include "common/libs/utils/files.h"
+#include "common/libs/utils/flag_parser.h"
+#include "common/libs/utils/result.h"
+#include "common/libs/utils/subprocess.h"
+#include "host/commands/cvd/server_constants.h"
+#include "host/libs/config/cuttlefish_config.h"
+#include "host/libs/config/known_paths.h"
+
+namespace cuttlefish {
+
+std::optional<std::string> GetCuttlefishConfigPath(const std::string& home) {
+ std::string home_realpath;
+ if (DirectoryExists(home)) {
+ CHECK(android::base::Realpath(home, &home_realpath));
+ static const char kSuffix[] = "/cuttlefish_assembly/cuttlefish_config.json";
+ std::string config_path = AbsolutePath(home_realpath + kSuffix);
+ if (FileExists(config_path)) {
+ return config_path;
+ }
+ }
+ return {};
+}
+
+InstanceManager::InstanceManager(InstanceLockFileManager& lock_manager)
+ : lock_manager_(lock_manager) {}
+
+bool InstanceManager::HasInstanceGroups() const {
+ std::lock_guard lock(instance_groups_mutex_);
+ return !instance_groups_.empty();
+}
+
+void InstanceManager::SetInstanceGroup(
+ const InstanceManager::InstanceGroupDir& dir,
+ const InstanceManager::InstanceGroupInfo& info) {
+ std::lock_guard assemblies_lock(instance_groups_mutex_);
+ instance_groups_[dir] = info;
+}
+
+void InstanceManager::RemoveInstanceGroup(
+ const InstanceManager::InstanceGroupDir& dir) {
+ std::lock_guard assemblies_lock(instance_groups_mutex_);
+ instance_groups_.erase(dir);
+}
+
+Result<InstanceManager::InstanceGroupInfo> InstanceManager::GetInstanceGroup(
+ const InstanceManager::InstanceGroupDir& dir) const {
+ std::lock_guard assemblies_lock(instance_groups_mutex_);
+ auto info_it = instance_groups_.find(dir);
+ if (info_it == instance_groups_.end()) {
+ return CF_ERR("No group dir \"" << dir << "\"");
+ } else {
+ return info_it->second;
+ }
+}
+
+cvd::Status InstanceManager::CvdFleet(const SharedFD& out,
+ const std::string& env_config) const {
+ std::lock_guard assemblies_lock(instance_groups_mutex_);
+ const char _GroupDeviceInfoStart[] = "[\n";
+ const char _GroupDeviceInfoSeparate[] = ",\n";
+ const char _GroupDeviceInfoEnd[] = "]\n";
+ WriteAll(out, _GroupDeviceInfoStart);
+ for (const auto& [group_dir, group_info] : instance_groups_) {
+ auto config_path = GetCuttlefishConfigPath(group_dir);
+ if (FileExists(env_config)) {
+ config_path = env_config;
+ }
+ if (config_path) {
+ // Reads CuttlefishConfig::instance_names(), which must remain stable
+ // across changes to config file format (within server_constants.h major
+ // version).
+ auto config = CuttlefishConfig::GetFromFile(*config_path);
+ if (config) {
+ Command command(group_info.host_binaries_dir + kStatusBin);
+ command.AddParameter("--print");
+ command.AddParameter("--all_instances");
+ command.RedirectStdIO(Subprocess::StdIOChannel::kStdOut, out);
+ command.AddEnvironmentVariable(kCuttlefishConfigEnvVarName,
+ *config_path);
+ if (int wait_result = command.Start().Wait(); wait_result != 0) {
+ WriteAll(out, " (unknown instance status error)");
+ }
+ }
+ }
+ if (group_dir != instance_groups_.rbegin()->first) {
+ WriteAll(out, _GroupDeviceInfoSeparate);
+ }
+ }
+ WriteAll(out, _GroupDeviceInfoEnd);
+ cvd::Status status;
+ status.set_code(cvd::Status::OK);
+ return status;
+}
+
+cvd::Status InstanceManager::CvdClear(const SharedFD& out,
+ const SharedFD& err) {
+ std::lock_guard lock(instance_groups_mutex_);
+ cvd::Status status;
+ for (const auto& [group_dir, group_info] : instance_groups_) {
+ auto config_path = GetCuttlefishConfigPath(group_dir);
+ if (config_path) {
+ // Stop all instances that are using this group dir.
+ Command command(group_info.host_binaries_dir + kStopBin);
+ // Delete the instance dirs.
+ command.AddParameter("--clear_instance_dirs");
+ command.RedirectStdIO(Subprocess::StdIOChannel::kStdOut, out);
+ command.RedirectStdIO(Subprocess::StdIOChannel::kStdErr, err);
+ command.AddEnvironmentVariable(kCuttlefishConfigEnvVarName, *config_path);
+ if (int wait_result = command.Start().Wait(); wait_result != 0) {
+ WriteAll(
+ out,
+ "Warning: error stopping instances for dir \"" + group_dir +
+ "\".\nThis can happen if instances are already stopped.\n");
+ }
+ for (const auto& instance : group_info.instances) {
+ auto lock = lock_manager_.TryAcquireLock(instance);
+ if (lock.ok() && (*lock)) {
+ (*lock)->Status(InUseState::kNotInUse);
+ }
+ }
+ }
+ }
+ RemoveFile(StringFromEnv("HOME", ".") + "/cuttlefish_runtime");
+ RemoveFile(GetGlobalConfigFileLink());
+ WriteAll(out, "Stopped all known instances\n");
+
+ instance_groups_.clear();
+ status.set_code(cvd::Status::OK);
+ return status;
+}
+
+} // namespace cuttlefish
diff --git a/host/commands/cvd/instance_manager.h b/host/commands/cvd/instance_manager.h
new file mode 100644
index 0000000..6aebbdb
--- /dev/null
+++ b/host/commands/cvd/instance_manager.h
@@ -0,0 +1,65 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <map>
+#include <mutex>
+#include <optional>
+#include <string>
+
+#include <fruit/fruit.h>
+
+#include "cvd_server.pb.h"
+
+#include "common/libs/fs/shared_fd.h"
+#include "common/libs/utils/result.h"
+#include "host/commands/cvd/instance_lock.h"
+
+namespace cuttlefish {
+
+constexpr char kStatusBin[] = "cvd_internal_status";
+constexpr char kStopBin[] = "cvd_internal_stop";
+
+class InstanceManager {
+ public:
+ using InstanceGroupDir = std::string;
+ struct InstanceGroupInfo {
+ std::string host_binaries_dir;
+ std::set<int> instances;
+ };
+
+ INJECT(InstanceManager(InstanceLockFileManager&));
+
+ bool HasInstanceGroups() const;
+ void SetInstanceGroup(const InstanceGroupDir&, const InstanceGroupInfo&);
+ void RemoveInstanceGroup(const InstanceGroupDir&);
+ Result<InstanceGroupInfo> GetInstanceGroup(const InstanceGroupDir&) const;
+
+ cvd::Status CvdClear(const SharedFD& out, const SharedFD& err);
+ cvd::Status CvdFleet(const SharedFD& out, const std::string& envconfig) const;
+
+ private:
+ InstanceLockFileManager& lock_manager_;
+
+ mutable std::mutex instance_groups_mutex_;
+ std::map<InstanceGroupDir, InstanceGroupInfo> instance_groups_;
+};
+
+std::optional<std::string> GetCuttlefishConfigPath(
+ const std::string& assembly_dir);
+
+} // namespace cuttlefish
diff --git a/host/commands/cvd/main.cc b/host/commands/cvd/main.cc
new file mode 100644
index 0000000..4e77852
--- /dev/null
+++ b/host/commands/cvd/main.cc
@@ -0,0 +1,380 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <stdlib.h>
+#include <chrono>
+#include <iostream>
+#include <map>
+#include <optional>
+#include <string>
+#include <thread>
+#include <vector>
+
+#include <android-base/file.h>
+#include <android-base/logging.h>
+#include <android-base/result.h>
+#include <build/version.h>
+
+#include "cvd_server.pb.h"
+
+#include "common/libs/fs/shared_fd.h"
+#include "common/libs/utils/environment.h"
+#include "common/libs/utils/files.h"
+#include "common/libs/utils/flag_parser.h"
+#include "common/libs/utils/result.h"
+#include "common/libs/utils/shared_fd_flag.h"
+#include "common/libs/utils/subprocess.h"
+#include "common/libs/utils/unix_sockets.h"
+#include "host/commands/cvd/server.h"
+#include "host/commands/cvd/server_constants.h"
+#include "host/libs/config/cuttlefish_config.h"
+#include "host/libs/config/host_tools_version.h"
+
+namespace cuttlefish {
+namespace {
+
+Result<SharedFD> ConnectToServer() {
+ auto connection =
+ SharedFD::SocketLocalClient(cvd::kServerSocketPath,
+ /*is_abstract=*/true, SOCK_SEQPACKET);
+ if (!connection->IsOpen()) {
+ auto connection =
+ SharedFD::SocketLocalClient(cvd::kServerSocketPath,
+ /*is_abstract=*/true, SOCK_STREAM);
+ }
+ if (!connection->IsOpen()) {
+ return CF_ERR("Failed to connect to server" << connection->StrError());
+ }
+ return connection;
+}
+
+class CvdClient {
+ public:
+ Result<void> EnsureCvdServerRunning(const std::string& host_tool_directory,
+ int num_retries = 1) {
+ cvd::Request request;
+ request.mutable_version_request();
+ auto response = SendRequest(request);
+
+ // If cvd_server is not running, start and wait before checking its version.
+ if (!response.ok()) {
+ CF_EXPECT(StartCvdServer(host_tool_directory));
+ response = CF_EXPECT(SendRequest(request));
+ }
+ CF_EXPECT(CheckStatus(response->status(), "GetVersion"));
+ CF_EXPECT(response->has_version_response(),
+ "GetVersion call missing VersionResponse.");
+
+ auto server_version = response->version_response().version();
+ if (server_version.major() != cvd::kVersionMajor) {
+ return CF_ERR("Major version difference: cvd("
+ << cvd::kVersionMajor << "." << cvd::kVersionMinor
+ << ") != cvd_server(" << server_version.major() << "."
+ << server_version.minor()
+ << "). Try `cvd kill-server` or `pkill cvd_server`.");
+ }
+ if (server_version.minor() < cvd::kVersionMinor) {
+ std::cerr << "Minor version of cvd_server is older than latest. "
+ << "Attempting to restart..." << std::endl;
+ CF_EXPECT(StopCvdServer(/*clear=*/false));
+ CF_EXPECT(StartCvdServer(host_tool_directory));
+ if (num_retries > 0) {
+ CF_EXPECT(EnsureCvdServerRunning(host_tool_directory, num_retries - 1));
+ return {};
+ } else {
+ return CF_ERR("Unable to start the cvd_server with version "
+ << cvd::kVersionMajor << "." << cvd::kVersionMinor);
+ }
+ }
+ if (server_version.build() != android::build::GetBuildNumber()) {
+ LOG(VERBOSE) << "cvd_server client version ("
+ << android::build::GetBuildNumber()
+ << ") does not match server version ("
+ << server_version.build() << std::endl;
+ }
+ auto self_crc32 = FileCrc("/proc/self/exe");
+ if (server_version.crc32() != self_crc32) {
+ LOG(VERBOSE) << "cvd_server client checksum (" << self_crc32
+ << ") doesn't match server checksum ("
+ << server_version.crc32() << std::endl;
+ }
+ return {};
+ }
+
+ Result<void> StopCvdServer(bool clear) {
+ if (!server_) {
+ // server_ may not represent a valid connection even while the server is
+ // running, if we haven't tried to connect. This establishes first whether
+ // the server is running.
+ auto connection_attempt = ConnectToServer();
+ if (!connection_attempt.ok()) {
+ return {};
+ }
+ }
+
+ cvd::Request request;
+ auto shutdown_request = request.mutable_shutdown_request();
+ if (clear) {
+ shutdown_request->set_clear(true);
+ }
+
+ // Send the server a pipe with the Shutdown request that it
+ // will close when it fully exits.
+ SharedFD read_pipe, write_pipe;
+ CF_EXPECT(cuttlefish::SharedFD::Pipe(&read_pipe, &write_pipe),
+ "Unable to create shutdown pipe: " << strerror(errno));
+
+ auto response = SendRequest(request, /*extra_fd=*/write_pipe);
+
+ // If the server is already not running then SendRequest will fail.
+ // We treat this as success.
+ if (!response.ok()) {
+ server_.reset();
+ return {};
+ }
+
+ CF_EXPECT(CheckStatus(response->status(), "Shutdown"));
+ CF_EXPECT(response->has_shutdown_response(),
+ "Shutdown call missing ShutdownResponse.");
+
+ // Clear out the server_ socket.
+ server_.reset();
+
+ // Close the write end of the pipe in this process. Now the only
+ // process that may have the write end still open is the cvd_server.
+ write_pipe->Close();
+
+ // Wait for the pipe to close by attempting to read from the pipe.
+ char buf[1]; // Any size >0 should work for read attempt.
+ CF_EXPECT(read_pipe->Read(buf, sizeof(buf)) <= 0,
+ "Unexpected read value from cvd_server shutdown pipe.");
+ return {};
+ }
+
+ Result<void> HandleCommand(std::vector<std::string> args,
+ std::vector<std::string> env) {
+ cvd::Request request;
+ auto command_request = request.mutable_command_request();
+ for (const std::string& arg : args) {
+ command_request->add_args(arg);
+ }
+ for (const std::string& e : env) {
+ auto eq_pos = e.find('=');
+ if (eq_pos == std::string::npos) {
+ LOG(WARNING) << "Environment var in unknown format: " << e;
+ continue;
+ }
+ (*command_request->mutable_env())[e.substr(0, eq_pos)] =
+ e.substr(eq_pos + 1);
+ }
+ std::unique_ptr<char, void(*)(void*)> cwd(getcwd(nullptr, 0), &free);
+ command_request->set_working_directory(cwd.get());
+ command_request->set_wait_behavior(cvd::WAIT_BEHAVIOR_COMPLETE);
+
+ auto response = CF_EXPECT(SendRequest(request));
+ CF_EXPECT(CheckStatus(response.status(), "HandleCommand"));
+ CF_EXPECT(response.has_command_response(),
+ "HandleCommand call missing CommandResponse.");
+ return {};
+ }
+
+ private:
+ std::optional<UnixMessageSocket> server_;
+
+ Result<void> SetServer(const SharedFD& server) {
+ CF_EXPECT(!server_, "Already have a server");
+ CF_EXPECT(server->IsOpen(), server->StrError());
+ server_ = UnixMessageSocket(server);
+ CF_EXPECT(server_->EnableCredentials(true).ok(),
+ "Unable to enable UnixMessageSocket credentials.");
+ return {};
+ }
+
+ Result<cvd::Response> SendRequest(const cvd::Request& request,
+ std::optional<SharedFD> extra_fd = {}) {
+ if (!server_) {
+ CF_EXPECT(SetServer(CF_EXPECT(ConnectToServer())));
+ }
+ // Serialize and send the request.
+ std::string serialized;
+ CF_EXPECT(request.SerializeToString(&serialized),
+ "Unable to serialize request proto.");
+ UnixSocketMessage request_message;
+
+ std::vector<SharedFD> control_fds = {
+ SharedFD::Dup(0),
+ SharedFD::Dup(1),
+ SharedFD::Dup(2),
+ };
+ if (extra_fd) {
+ control_fds.push_back(*extra_fd);
+ }
+ auto control = CF_EXPECT(ControlMessage::FromFileDescriptors(control_fds));
+ request_message.control.emplace_back(std::move(control));
+
+ request_message.data =
+ std::vector<char>(serialized.begin(), serialized.end());
+ CF_EXPECT(server_->WriteMessage(request_message));
+
+ // Read and parse the response.
+ auto read_result = CF_EXPECT(server_->ReadMessage());
+ serialized = std::string(read_result.data.begin(), read_result.data.end());
+ cvd::Response response;
+ CF_EXPECT(response.ParseFromString(serialized),
+ "Unable to parse serialized response proto.");
+ return response;
+ }
+
+ Result<void> StartCvdServer(const std::string& host_tool_directory) {
+ SharedFD server_fd =
+ SharedFD::SocketLocalServer(cvd::kServerSocketPath,
+ /*is_abstract=*/true, SOCK_SEQPACKET, 0666);
+ CF_EXPECT(server_fd->IsOpen(), server_fd->StrError());
+
+ // TODO(b/196114111): Investigate fully "daemonizing" the cvd_server.
+ CF_EXPECT(setenv("ANDROID_HOST_OUT", host_tool_directory.c_str(),
+ /*overwrite=*/true) == 0);
+ Command command("/proc/self/exe");
+ command.AddParameter("-INTERNAL_server_fd=", server_fd);
+ SubprocessOptions options;
+ options.ExitWithParent(false);
+ command.Start(options);
+
+ // Connect to the server_fd, which waits for startup.
+ CF_EXPECT(SetServer(SharedFD::SocketLocalClient(cvd::kServerSocketPath,
+ /*is_abstract=*/true,
+ SOCK_SEQPACKET)));
+ return {};
+ }
+
+ Result<void> CheckStatus(const cvd::Status& status, const std::string& rpc) {
+ if (status.code() == cvd::Status::OK) {
+ return {};
+ }
+ return CF_ERR("Received error response for \"" << rpc << "\":\n"
+ << status.message()
+ << "\nIn client");
+ }
+};
+
+[[noreturn]] void CallPythonAcloud(std::vector<std::string>& args) {
+ auto android_top = StringFromEnv("ANDROID_BUILD_TOP", "");
+ if (android_top == "") {
+ LOG(FATAL) << "Could not find android environment. Please run "
+ << "\"source build/envsetup.sh\".";
+ abort();
+ }
+ // TODO(b/206893146): Detect what the platform actually is.
+ auto py_acloud_path =
+ android_top + "/prebuilts/asuite/acloud/linux-x86/acloud";
+ char** new_argv = new char*[args.size() + 1];
+ for (size_t i = 0; i < args.size(); i++) {
+ new_argv[i] = args[i].data();
+ }
+ new_argv[args.size()] = nullptr;
+ execv(py_acloud_path.data(), new_argv);
+ PLOG(FATAL) << "execv(" << py_acloud_path << ", ...) failed";
+ abort();
+}
+
+Result<int> CvdMain(int argc, char** argv, char** envp) {
+ android::base::InitLogging(argv, android::base::StderrLogger);
+
+ std::vector<std::string> args = ArgsToVec(argc, argv);
+ std::vector<Flag> flags;
+
+ CvdClient client;
+
+ // TODO(b/206893146): Make this decision inside the server.
+ if (args[0] == "acloud") {
+ auto server_running = client.EnsureCvdServerRunning(
+ android::base::Dirname(android::base::GetExecutableDirectory()));
+ if (server_running.ok()) {
+ // TODO(schuffelen): Deduplicate when calls to setenv are removed.
+ std::vector<std::string> env;
+ for (char** e = envp; *e != 0; e++) {
+ env.emplace_back(*e);
+ }
+ args[0] = "try-acloud";
+ auto attempt = client.HandleCommand(args, env);
+ if (attempt.ok()) {
+ args[0] = "acloud";
+ CF_EXPECT(client.HandleCommand(args, env));
+ return 0;
+ } else {
+ CallPythonAcloud(args);
+ }
+ } else {
+ // Something is wrong with the server, fall back to python acloud
+ CallPythonAcloud(args);
+ }
+ }
+ bool clean = false;
+ flags.emplace_back(GflagsCompatFlag("clean", clean));
+ SharedFD internal_server_fd;
+ flags.emplace_back(SharedFDFlag("INTERNAL_server_fd", internal_server_fd));
+
+ CF_EXPECT(ParseFlags(flags, args));
+
+ if (internal_server_fd->IsOpen()) {
+ return CF_EXPECT(CvdServerMain(internal_server_fd));
+ } else if (argv[0] == std::string("/proc/self/exe")) {
+ return CF_ERR(
+ "Expected to be in server mode, but didn't get a server "
+ "fd: "
+ << internal_server_fd->StrError());
+ }
+
+ // Special case for `cvd kill-server`, handled by directly
+ // stopping the cvd_server.
+ if (argc > 1 && strcmp("kill-server", argv[1]) == 0) {
+ CF_EXPECT(client.StopCvdServer(/*clear=*/true));
+ return 0;
+ }
+
+ // Special case for --clean flag, used to clear any existing state.
+ if (clean) {
+ LOG(INFO) << "cvd invoked with --clean; "
+ << "stopping the cvd_server before continuing.";
+ CF_EXPECT(client.StopCvdServer(/*clear=*/true));
+ }
+
+ // Handle all remaining commands by forwarding them to the cvd_server.
+ CF_EXPECT(client.EnsureCvdServerRunning(android::base::Dirname(
+ android::base::GetExecutableDirectory())),
+ "Unable to ensure cvd_server is running.");
+
+ // TODO(schuffelen): Deduplicate when calls to setenv are removed.
+ std::vector<std::string> env;
+ for (char** e = envp; *e != 0; e++) {
+ env.emplace_back(*e);
+ }
+ CF_EXPECT(client.HandleCommand(args, env));
+ return 0;
+}
+
+} // namespace
+} // namespace cuttlefish
+
+int main(int argc, char** argv, char** envp) {
+ auto result = cuttlefish::CvdMain(argc, argv, envp);
+ if (result.ok()) {
+ return *result;
+ } else {
+ std::cerr << result.error() << std::endl;
+ return -1;
+ }
+}
diff --git a/common/libs/utils/size_utils.cpp b/host/commands/cvd/proto/Android.bp
similarity index 60%
copy from common/libs/utils/size_utils.cpp
copy to host/commands/cvd/proto/Android.bp
index 9f25445..75de449 100644
--- a/common/libs/utils/size_utils.cpp
+++ b/host/commands/cvd/proto/Android.bp
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2018 The Android Open Source Project
+ * Copyright (C) 2021 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -14,15 +14,17 @@
* limitations under the License.
*/
-#include "common/libs/utils/size_utils.h"
-
-#include <unistd.h>
-
-namespace cuttlefish {
-
-uint64_t AlignToPowerOf2(uint64_t val, uint8_t align_log) {
- uint64_t align = 1ULL << align_log;
- return ((val + (align - 1)) / align) * align;
+package {
+ default_applicable_licenses: ["Android-Apache-2.0"],
}
-} // namespace cuttlefish
+cc_library_static {
+ name: "libcuttlefish_cvd_proto",
+ host_supported: true,
+ proto: {
+ export_proto_headers: true,
+ type: "lite",
+ //include_dirs: ["external/protobuf/src"],
+ },
+ srcs: ["cvd_server.proto"],
+}
diff --git a/host/commands/cvd/proto/cvd_server.proto b/host/commands/cvd/proto/cvd_server.proto
new file mode 100644
index 0000000..903f03e
--- /dev/null
+++ b/host/commands/cvd/proto/cvd_server.proto
@@ -0,0 +1,85 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+syntax = "proto3";
+
+package cuttlefish.cvd;
+
+message Status {
+ // Subset of status codes from gRPC.
+ enum Code {
+ OK = 0;
+ FAILED_PRECONDITION = 9;
+ INTERNAL = 13;
+ }
+
+ Code code = 1;
+ string message = 2;
+}
+
+message Request {
+ oneof contents {
+ // Returns the version of the CvdServer.
+ VersionRequest version_request = 1;
+ // Requests the CvdServer to shutdown.
+ ShutdownRequest shutdown_request = 2;
+ // Requests the CvdServer to execute a command on behalf of the client.
+ CommandRequest command_request = 3;
+ }
+}
+
+message Response {
+ Status status = 1;
+ oneof contents {
+ VersionResponse version_response = 2;
+ ShutdownResponse shutdown_response = 3;
+ CommandResponse command_response = 4;
+ }
+}
+
+message Version {
+ int32 major = 1;
+ int32 minor = 2;
+ string build = 3;
+ uint32 crc32 = 4;
+}
+
+message VersionRequest {}
+message VersionResponse {
+ Version version = 1;
+}
+
+message ShutdownRequest {
+ // If true, clears instance and assembly state before shutting down.
+ bool clear = 1;
+}
+message ShutdownResponse {}
+
+enum WaitBehavior {
+ WAIT_BEHAVIOR_UNKNOWN = 0;
+ WAIT_BEHAVIOR_START = 1;
+ WAIT_BEHAVIOR_COMPLETE = 2;
+}
+
+message CommandRequest {
+ // The args that should be executed, including the subcommand.
+ repeated string args = 1;
+ // Environment variables that will be used by the subcommand.
+ map<string, string> env = 2;
+ string working_directory = 3;
+ WaitBehavior wait_behavior = 4;
+}
+message CommandResponse {}
diff --git a/host/commands/cvd/scope_guard.cpp b/host/commands/cvd/scope_guard.cpp
new file mode 100644
index 0000000..a0e8bbb
--- /dev/null
+++ b/host/commands/cvd/scope_guard.cpp
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#include "host/commands/cvd/scope_guard.h"
+
+#include <functional>
+
+namespace cuttlefish {
+
+ScopeGuard::ScopeGuard() = default;
+
+ScopeGuard::ScopeGuard(std::function<void()> fn) : fn_(fn) {}
+
+ScopeGuard::ScopeGuard(ScopeGuard&&) = default;
+
+ScopeGuard& ScopeGuard::operator=(ScopeGuard&&) = default;
+
+ScopeGuard::~ScopeGuard() {
+ if (fn_) {
+ fn_();
+ }
+}
+
+void ScopeGuard::Cancel() { fn_ = nullptr; }
+
+} // namespace cuttlefish
diff --git a/common/libs/utils/size_utils.cpp b/host/commands/cvd/scope_guard.h
similarity index 64%
copy from common/libs/utils/size_utils.cpp
copy to host/commands/cvd/scope_guard.h
index 9f25445..596bc66 100644
--- a/common/libs/utils/size_utils.cpp
+++ b/host/commands/cvd/scope_guard.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2018 The Android Open Source Project
+ * Copyright (C) 2022 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -13,16 +13,24 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
+#pragma once
-#include "common/libs/utils/size_utils.h"
-
-#include <unistd.h>
+#include <functional>
namespace cuttlefish {
-uint64_t AlignToPowerOf2(uint64_t val, uint8_t align_log) {
- uint64_t align = 1ULL << align_log;
- return ((val + (align - 1)) / align) * align;
-}
+class ScopeGuard {
+ public:
+ ScopeGuard();
+ explicit ScopeGuard(std::function<void()> fn);
+ ScopeGuard(ScopeGuard&&);
+ ~ScopeGuard();
+ ScopeGuard& operator=(ScopeGuard&&);
+
+ void Cancel();
+
+ private:
+ std::function<void()> fn_;
+};
} // namespace cuttlefish
diff --git a/host/commands/cvd/server.cc b/host/commands/cvd/server.cc
new file mode 100644
index 0000000..6e74ddf
--- /dev/null
+++ b/host/commands/cvd/server.cc
@@ -0,0 +1,290 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "host/commands/cvd/server.h"
+
+#include <signal.h>
+
+#include <atomic>
+#include <future>
+#include <map>
+#include <mutex>
+#include <optional>
+#include <thread>
+
+#include <android-base/file.h>
+#include <android-base/logging.h>
+#include <fruit/fruit.h>
+
+#include "cvd_server.pb.h"
+
+#include "common/libs/fs/shared_buf.h"
+#include "common/libs/fs/shared_fd.h"
+#include "common/libs/fs/shared_select.h"
+#include "common/libs/utils/files.h"
+#include "common/libs/utils/flag_parser.h"
+#include "common/libs/utils/result.h"
+#include "common/libs/utils/shared_fd_flag.h"
+#include "common/libs/utils/subprocess.h"
+#include "host/commands/cvd/epoll_loop.h"
+#include "host/commands/cvd/scope_guard.h"
+#include "host/commands/cvd/server_constants.h"
+#include "host/libs/config/cuttlefish_config.h"
+#include "host/libs/config/known_paths.h"
+
+namespace cuttlefish {
+
+static fruit::Component<> RequestComponent(CvdServer* server,
+ InstanceManager* instance_manager) {
+ return fruit::createComponent()
+ .bindInstance(*server)
+ .bindInstance(*instance_manager)
+ .install(AcloudCommandComponent)
+ .install(cvdCommandComponent)
+ .install(cvdShutdownComponent)
+ .install(cvdVersionComponent);
+}
+
+static constexpr int kNumThreads = 10;
+
+CvdServer::CvdServer(EpollPool& epoll_pool, InstanceManager& instance_manager)
+ : epoll_pool_(epoll_pool),
+ instance_manager_(instance_manager),
+ running_(true) {
+ std::scoped_lock lock(threads_mutex_);
+ for (auto i = 0; i < kNumThreads; i++) {
+ threads_.emplace_back([this]() {
+ while (running_) {
+ auto result = epoll_pool_.HandleEvent();
+ if (!result.ok()) {
+ LOG(ERROR) << "Epoll worker error:\n" << result.error();
+ }
+ }
+ auto wakeup = BestEffortWakeup();
+ CHECK(wakeup.ok()) << wakeup.error().message();
+ });
+ }
+}
+
+CvdServer::~CvdServer() {
+ running_ = false;
+ auto wakeup = BestEffortWakeup();
+ CHECK(wakeup.ok()) << wakeup.error().message();
+ Join();
+}
+
+Result<void> CvdServer::BestEffortWakeup() {
+ // This attempts to cascade through the responder threads, forcing them
+ // to wake up and see that running_ is false, then exit and wake up
+ // further threads.
+ auto eventfd = SharedFD::Event();
+ CF_EXPECT(eventfd->IsOpen(), eventfd->StrError());
+ CF_EXPECT(eventfd->EventfdWrite(1) == 0, eventfd->StrError());
+
+ auto cb = [](EpollEvent) -> Result<void> { return {}; };
+ CF_EXPECT(epoll_pool_.Register(eventfd, EPOLLIN, cb));
+ return {};
+}
+
+void CvdServer::Stop() {
+ {
+ std::lock_guard lock(ongoing_requests_mutex_);
+ running_ = false;
+ }
+ while (true) {
+ std::shared_ptr<OngoingRequest> request;
+ {
+ std::lock_guard lock(ongoing_requests_mutex_);
+ if (ongoing_requests_.empty()) {
+ break;
+ }
+ auto it = ongoing_requests_.begin();
+ request = *it;
+ ongoing_requests_.erase(it);
+ }
+ {
+ std::lock_guard lock(request->mutex);
+ if (request->handler == nullptr) {
+ continue;
+ }
+ request->handler->Interrupt();
+ }
+ std::scoped_lock lock(threads_mutex_);
+ for (auto& thread : threads_) {
+ auto current_thread = thread.get_id() == std::this_thread::get_id();
+ auto matching_thread = thread.get_id() == request->thread_id;
+ if (!current_thread && matching_thread && thread.joinable()) {
+ thread.join();
+ }
+ }
+ }
+}
+
+void CvdServer::Join() {
+ for (auto& thread : threads_) {
+ if (thread.joinable()) {
+ thread.join();
+ }
+ }
+}
+
+static Result<CvdServerHandler*> RequestHandler(
+ const RequestWithStdio& request,
+ const std::vector<CvdServerHandler*>& handlers) {
+ Result<cvd::Response> response;
+ std::vector<CvdServerHandler*> compatible_handlers;
+ for (auto& handler : handlers) {
+ if (CF_EXPECT(handler->CanHandle(request))) {
+ compatible_handlers.push_back(handler);
+ }
+ }
+ CF_EXPECT(compatible_handlers.size() == 1,
+ "Expected exactly one handler for message, found "
+ << compatible_handlers.size());
+ return compatible_handlers[0];
+}
+
+Result<void> CvdServer::StartServer(SharedFD server_fd) {
+ auto cb = [this](EpollEvent ev) -> Result<void> {
+ CF_EXPECT(AcceptClient(ev));
+ return {};
+ };
+ CF_EXPECT(epoll_pool_.Register(server_fd, EPOLLIN, cb));
+ return {};
+}
+
+Result<void> CvdServer::AcceptClient(EpollEvent event) {
+ ScopeGuard stop_on_failure([this] { Stop(); });
+
+ CF_EXPECT(event.events & EPOLLIN);
+ auto client_fd = SharedFD::Accept(*event.fd);
+ CF_EXPECT(client_fd->IsOpen(), client_fd->StrError());
+ auto client_cb = [this](EpollEvent ev) -> Result<void> {
+ CF_EXPECT(HandleMessage(ev));
+ return {};
+ };
+ CF_EXPECT(epoll_pool_.Register(client_fd, EPOLLIN, client_cb));
+
+ auto self_cb = [this](EpollEvent ev) -> Result<void> {
+ CF_EXPECT(AcceptClient(ev));
+ return {};
+ };
+ CF_EXPECT(epoll_pool_.Register(event.fd, EPOLLIN, self_cb));
+
+ stop_on_failure.Cancel();
+ return {};
+}
+
+Result<void> CvdServer::HandleMessage(EpollEvent event) {
+ ScopeGuard abandon_client([this, event] { epoll_pool_.Remove(event.fd); });
+
+ if (event.events & EPOLLHUP) { // Client went away.
+ epoll_pool_.Remove(event.fd);
+ return {};
+ }
+
+ CF_EXPECT(event.events & EPOLLIN);
+ auto request = CF_EXPECT(GetRequest(event.fd));
+ if (!request) { // End-of-file / client went away.
+ epoll_pool_.Remove(event.fd);
+ return {};
+ }
+
+ auto response = HandleRequest(*request, event.fd);
+ if (!response.ok()) {
+ cvd::Response failure_message;
+ failure_message.mutable_status()->set_code(cvd::Status::INTERNAL);
+ failure_message.mutable_status()->set_message(response.error().message());
+ CF_EXPECT(SendResponse(event.fd, failure_message));
+ return {}; // Error already sent to the client, don't repeat on the server
+ }
+ CF_EXPECT(SendResponse(event.fd, *response));
+
+ auto self_cb = [this](EpollEvent ev) -> Result<void> {
+ CF_EXPECT(HandleMessage(ev));
+ return {};
+ };
+ CF_EXPECT(epoll_pool_.Register(event.fd, EPOLLIN, self_cb));
+
+ abandon_client.Cancel();
+ return {};
+}
+
+Result<cvd::Response> CvdServer::HandleRequest(RequestWithStdio request,
+ SharedFD client) {
+ fruit::Injector<> injector(RequestComponent, this, &instance_manager_);
+ auto possible_handlers = injector.getMultibindings<CvdServerHandler>();
+
+ // Even if the interrupt callback outlives the request handler, it'll only
+ // hold on to this struct which will be cleaned out when the request handler
+ // exits.
+ auto shared = std::make_shared<OngoingRequest>();
+ shared->handler = CF_EXPECT(RequestHandler(request, possible_handlers));
+ shared->thread_id = std::this_thread::get_id();
+
+ {
+ std::lock_guard lock(ongoing_requests_mutex_);
+ if (running_) {
+ ongoing_requests_.insert(shared);
+ } else {
+ // We're executing concurrently with a Stop() call.
+ return {};
+ }
+ }
+ ScopeGuard remove_ongoing_request([this, shared] {
+ std::lock_guard lock(ongoing_requests_mutex_);
+ ongoing_requests_.erase(shared);
+ });
+
+ auto interrupt_cb = [shared](EpollEvent) -> Result<void> {
+ std::lock_guard lock(shared->mutex);
+ CF_EXPECT(shared->handler != nullptr);
+ CF_EXPECT(shared->handler->Interrupt());
+ return {};
+ };
+ CF_EXPECT(epoll_pool_.Register(client, EPOLLHUP, interrupt_cb));
+
+ auto response = CF_EXPECT(shared->handler->Handle(request));
+ {
+ std::lock_guard lock(shared->mutex);
+ shared->handler = nullptr;
+ }
+ CF_EXPECT(epoll_pool_.Remove(client)); // Delete interrupt handler
+
+ return response;
+}
+
+static fruit::Component<CvdServer> ServerComponent() {
+ return fruit::createComponent()
+ .install(EpollLoopComponent);
+}
+
+Result<int> CvdServerMain(SharedFD server_fd) {
+ LOG(INFO) << "Starting server";
+
+ signal(SIGPIPE, SIG_IGN);
+
+ CF_EXPECT(server_fd->IsOpen(), "Did not receive a valid cvd_server fd");
+
+ fruit::Injector<CvdServer> injector(ServerComponent);
+ CvdServer& server = injector.get<CvdServer&>();
+ server.StartServer(server_fd);
+ server.Join();
+
+ return 0;
+}
+
+} // namespace cuttlefish
diff --git a/host/commands/cvd/server.h b/host/commands/cvd/server.h
new file mode 100644
index 0000000..8a77081
--- /dev/null
+++ b/host/commands/cvd/server.h
@@ -0,0 +1,112 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <atomic>
+#include <map>
+#include <optional>
+#include <shared_mutex>
+#include <string>
+#include <vector>
+
+#include <fruit/fruit.h>
+
+#include "cvd_server.pb.h"
+
+#include "common/libs/fs/epoll.h"
+#include "common/libs/fs/shared_fd.h"
+#include "common/libs/utils/result.h"
+#include "common/libs/utils/subprocess.h"
+#include "common/libs/utils/unix_sockets.h"
+#include "host/commands/cvd/epoll_loop.h"
+#include "host/commands/cvd/instance_manager.h"
+#include "host/commands/cvd/server_client.h"
+
+namespace cuttlefish {
+
+class CvdServerHandler {
+ public:
+ virtual ~CvdServerHandler() = default;
+
+ virtual Result<bool> CanHandle(const RequestWithStdio&) const = 0;
+ virtual Result<cvd::Response> Handle(const RequestWithStdio&) = 0;
+ virtual Result<void> Interrupt() = 0;
+};
+
+class CvdServer {
+ public:
+ INJECT(CvdServer(EpollPool&, InstanceManager&));
+ ~CvdServer();
+
+ Result<void> StartServer(SharedFD server);
+ void Stop();
+ void Join();
+
+ private:
+ struct OngoingRequest {
+ CvdServerHandler* handler;
+ std::mutex mutex;
+ std::thread::id thread_id;
+ };
+
+ Result<void> AcceptClient(EpollEvent);
+ Result<void> HandleMessage(EpollEvent);
+ Result<cvd::Response> HandleRequest(RequestWithStdio, SharedFD client);
+ Result<void> BestEffortWakeup();
+
+ EpollPool& epoll_pool_;
+ InstanceManager& instance_manager_;
+ std::atomic_bool running_ = true;
+
+ std::mutex ongoing_requests_mutex_;
+ std::set<std::shared_ptr<OngoingRequest>> ongoing_requests_;
+ // TODO(schuffelen): Move this thread pool to another class.
+ std::mutex threads_mutex_;
+ std::vector<std::thread> threads_;
+};
+
+class CvdCommandHandler : public CvdServerHandler {
+ public:
+ INJECT(CvdCommandHandler(InstanceManager& instance_manager));
+
+ Result<bool> CanHandle(const RequestWithStdio&) const override;
+ Result<cvd::Response> Handle(const RequestWithStdio&) override;
+ Result<void> Interrupt() override;
+
+ private:
+ InstanceManager& instance_manager_;
+ std::optional<Subprocess> subprocess_;
+ std::mutex interruptible_;
+ bool interrupted_ = false;
+};
+
+fruit::Component<fruit::Required<InstanceManager>> cvdCommandComponent();
+fruit::Component<fruit::Required<CvdServer, InstanceManager>>
+cvdShutdownComponent();
+fruit::Component<> cvdVersionComponent();
+fruit::Component<fruit::Required<CvdCommandHandler>> AcloudCommandComponent();
+
+struct CommandInvocation {
+ std::string command;
+ std::vector<std::string> arguments;
+};
+
+CommandInvocation ParseInvocation(const cvd::Request& request);
+
+Result<int> CvdServerMain(SharedFD server_fd);
+
+} // namespace cuttlefish
diff --git a/host/commands/cvd/server_client.cpp b/host/commands/cvd/server_client.cpp
new file mode 100644
index 0000000..017b2f5
--- /dev/null
+++ b/host/commands/cvd/server_client.cpp
@@ -0,0 +1,119 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "host/commands/cvd/server_client.h"
+
+#include <atomic>
+#include <condition_variable>
+#include <memory>
+#include <optional>
+#include <queue>
+#include <thread>
+
+#include "cvd_server.pb.h"
+
+#include "common/libs/fs/shared_fd.h"
+#include "common/libs/fs/shared_select.h"
+#include "common/libs/utils/result.h"
+#include "common/libs/utils/unix_sockets.h"
+
+namespace cuttlefish {
+
+Result<UnixMessageSocket> GetClient(const SharedFD& client) {
+ UnixMessageSocket result(client);
+ CF_EXPECT(result.EnableCredentials(true),
+ "Unable to enable UnixMessageSocket credentials.");
+ return result;
+}
+
+Result<std::optional<RequestWithStdio>> GetRequest(const SharedFD& client) {
+ UnixMessageSocket reader =
+ CF_EXPECT(GetClient(client), "Couldn't get client");
+ auto read_result = CF_EXPECT(reader.ReadMessage(), "Couldn't read message");
+
+ if (read_result.data.empty()) {
+ LOG(VERBOSE) << "Read empty packet, so the client has probably closed the "
+ "connection.";
+ return {};
+ };
+
+ std::string serialized(read_result.data.begin(), read_result.data.end());
+ cvd::Request request;
+ CF_EXPECT(request.ParseFromString(serialized),
+ "Unable to parse serialized request proto.");
+
+ CF_EXPECT(read_result.HasFileDescriptors(),
+ "Missing stdio fds from request.");
+ auto fds = CF_EXPECT(read_result.FileDescriptors(),
+ "Error reading stdio fds from request");
+ CF_EXPECT(fds.size() == 3 || fds.size() == 4, "Wrong number of FDs, received "
+ << fds.size()
+ << ", wanted 3 or 4");
+
+ std::optional<ucred> creds;
+ if (read_result.HasCredentials()) {
+ // TODO(b/198453477): Use Credentials to control command access.
+ creds = CF_EXPECT(read_result.Credentials(), "Failed to get credentials");
+ LOG(DEBUG) << "Has credentials, uid=" << creds->uid;
+ }
+
+ return RequestWithStdio(std::move(request), std::move(fds), std::move(creds));
+}
+
+Result<void> SendResponse(const SharedFD& client,
+ const cvd::Response& response) {
+ std::string serialized;
+ CF_EXPECT(response.SerializeToString(&serialized),
+ "Unable to serialize response proto.");
+ UnixSocketMessage message;
+ message.data = std::vector<char>(serialized.begin(), serialized.end());
+
+ UnixMessageSocket writer =
+ CF_EXPECT(GetClient(client), "Couldn't get client");
+ CF_EXPECT(writer.WriteMessage(message));
+ return {};
+}
+
+RequestWithStdio::RequestWithStdio(cvd::Request message,
+ std::vector<SharedFD> fds,
+ std::optional<ucred> creds)
+ : message_(message), fds_(std::move(fds)), creds_(creds) {}
+
+const cvd::Request& RequestWithStdio::Message() const { return message_; }
+
+const std::vector<SharedFD>& RequestWithStdio::FileDescriptors() const {
+ return fds_;
+}
+
+SharedFD RequestWithStdio::In() const {
+ return fds_.size() > 0 ? fds_[0] : SharedFD();
+}
+
+SharedFD RequestWithStdio::Out() const {
+ return fds_.size() > 1 ? fds_[1] : SharedFD();
+}
+
+SharedFD RequestWithStdio::Err() const {
+ return fds_.size() > 2 ? fds_[2] : SharedFD();
+}
+
+std::optional<SharedFD> RequestWithStdio::Extra() const {
+ return fds_.size() > 3 ? fds_[3] : std::optional<SharedFD>{};
+}
+
+std::optional<ucred> RequestWithStdio::Credentials() const { return creds_; }
+
+} // namespace cuttlefish
diff --git a/host/commands/cvd/server_client.h b/host/commands/cvd/server_client.h
new file mode 100644
index 0000000..751a890
--- /dev/null
+++ b/host/commands/cvd/server_client.h
@@ -0,0 +1,56 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <sys/socket.h>
+
+#include <memory>
+#include <optional>
+#include <vector>
+
+#include "cvd_server.pb.h"
+
+#include "common/libs/fs/shared_fd.h"
+#include "common/libs/utils/result.h"
+#include "common/libs/utils/unix_sockets.h"
+
+namespace cuttlefish {
+
+class RequestWithStdio {
+ public:
+ RequestWithStdio(cvd::Request, std::vector<SharedFD>, std::optional<ucred>);
+
+ const cvd::Request& Message() const;
+ const std::vector<SharedFD>& FileDescriptors() const;
+ SharedFD In() const;
+ SharedFD Out() const;
+ SharedFD Err() const;
+ std::optional<SharedFD> Extra() const;
+ std::optional<ucred> Credentials() const;
+
+ private:
+ cvd::Request message_;
+ std::vector<SharedFD> fds_;
+ std::optional<ucred> creds_;
+};
+
+Result<UnixMessageSocket> GetClient(const SharedFD& client);
+Result<std::optional<RequestWithStdio>> GetRequest(const SharedFD& client);
+Result<void> SendResponse(const SharedFD& client,
+ const cvd::Response& response);
+
+} // namespace cuttlefish
diff --git a/host/commands/cvd/server_command.cpp b/host/commands/cvd/server_command.cpp
new file mode 100644
index 0000000..e7e9b4a
--- /dev/null
+++ b/host/commands/cvd/server_command.cpp
@@ -0,0 +1,338 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "host/commands/cvd/server.h"
+
+#include <set>
+#include <string>
+#include <vector>
+
+#include <android-base/file.h>
+#include <android-base/logging.h>
+#include <fruit/fruit.h>
+
+#include "cvd_server.pb.h"
+
+#include "common/libs/fs/shared_buf.h"
+#include "common/libs/fs/shared_fd.h"
+#include "common/libs/utils/environment.h"
+#include "common/libs/utils/files.h"
+#include "common/libs/utils/flag_parser.h"
+#include "common/libs/utils/result.h"
+#include "common/libs/utils/subprocess.h"
+#include "host/commands/cvd/instance_manager.h"
+#include "host/libs/config/cuttlefish_config.h"
+
+namespace cuttlefish {
+namespace {
+
+constexpr char kHostBugreportBin[] = "cvd_internal_host_bugreport";
+constexpr char kStartBin[] = "cvd_internal_start";
+constexpr char kFetchBin[] = "fetch_cvd";
+constexpr char kMkdirBin[] = "/bin/mkdir";
+
+constexpr char kClearBin[] = "clear_placeholder"; // Unused, runs CvdClear()
+constexpr char kFleetBin[] = "fleet_placeholder"; // Unused, runs CvdFleet()
+constexpr char kHelpBin[] = "help_placeholder"; // Unused, prints kHelpMessage.
+constexpr char kHelpMessage[] = R"(Cuttlefish Virtual Device (CVD) CLI.
+
+usage: cvd <command> <args>
+
+Commands:
+ help Print this message.
+ help <command> Print help for a command.
+ start Start a device.
+ stop Stop a running device.
+ clear Stop all running devices and delete all instance and assembly directories.
+ fleet View the current fleet status.
+ kill-server Kill the cvd_server background process.
+ status Check and print the state of a running instance.
+ host_bugreport Capture a host bugreport, including configs, logs, and tombstones.
+
+Args:
+ <command args> Each command has its own set of args. See cvd help <command>.
+ --clean If provided, runs cvd kill-server before the requested command.
+)";
+
+const std::map<std::string, std::string> CommandToBinaryMap = {
+ {"help", kHelpBin},
+ {"host_bugreport", kHostBugreportBin},
+ {"cvd_host_bugreport", kHostBugreportBin},
+ {"start", kStartBin},
+ {"launch_cvd", kStartBin},
+ {"status", kStatusBin},
+ {"cvd_status", kStatusBin},
+ {"stop", kStopBin},
+ {"stop_cvd", kStopBin},
+ {"clear", kClearBin},
+ {"fetch", kFetchBin},
+ {"fetch_cvd", kFetchBin},
+ {"mkdir", kMkdirBin},
+ {"fleet", kFleetBin}};
+
+} // namespace
+
+CvdCommandHandler::CvdCommandHandler(InstanceManager& instance_manager)
+ : instance_manager_(instance_manager) {}
+
+Result<bool> CvdCommandHandler::CanHandle(
+ const RequestWithStdio& request) const {
+ auto invocation = ParseInvocation(request.Message());
+ return CommandToBinaryMap.find(invocation.command) !=
+ CommandToBinaryMap.end();
+}
+
+Result<cvd::Response> CvdCommandHandler::Handle(
+ const RequestWithStdio& request) {
+ std::unique_lock interrupt_lock(interruptible_);
+ if (interrupted_) {
+ return CF_ERR("Interrupted");
+ }
+ CF_EXPECT(CanHandle(request));
+ cvd::Response response;
+ response.mutable_command_response();
+
+ auto invocation = ParseInvocation(request.Message());
+
+ auto subcommand_bin = CommandToBinaryMap.find(invocation.command);
+ CF_EXPECT(subcommand_bin != CommandToBinaryMap.end());
+ auto bin = subcommand_bin->second;
+
+ // HOME is used to possibly set CuttlefishConfig path env variable later. This
+ // env variable is used by subcommands when locating the config.
+ auto request_home = request.Message().command_request().env().find("HOME");
+ std::string home =
+ request_home != request.Message().command_request().env().end()
+ ? request_home->second
+ : StringFromEnv("HOME", ".");
+
+ // Create a copy of args before parsing, to be passed to subcommands.
+ auto args = invocation.arguments;
+ auto args_copy = invocation.arguments;
+
+ auto host_artifacts_path =
+ request.Message().command_request().env().find("ANDROID_HOST_OUT");
+ if (host_artifacts_path == request.Message().command_request().env().end()) {
+ response.mutable_status()->set_code(cvd::Status::FAILED_PRECONDITION);
+ response.mutable_status()->set_message(
+ "Missing ANDROID_HOST_OUT in client environment.");
+ return response;
+ }
+
+ if (bin == kHelpBin) {
+ // Handle `cvd help`
+ if (args.empty()) {
+ WriteAll(request.Out(), kHelpMessage);
+ response.mutable_status()->set_code(cvd::Status::OK);
+ return response;
+ }
+
+ // Certain commands have no detailed help text.
+ std::set<std::string> builtins = {"help", "clear", "kill-server"};
+ auto it = CommandToBinaryMap.find(args[0]);
+ if (it == CommandToBinaryMap.end() ||
+ builtins.find(args[0]) != builtins.end()) {
+ WriteAll(request.Out(), kHelpMessage);
+ response.mutable_status()->set_code(cvd::Status::OK);
+ return response;
+ }
+
+ // Handle `cvd help <subcommand>` by calling the subcommand with --help.
+ bin = it->second;
+ args_copy.push_back("--help");
+ } else if (bin == kClearBin) {
+ *response.mutable_status() =
+ instance_manager_.CvdClear(request.Out(), request.Err());
+ return response;
+ } else if (bin == kFleetBin) {
+ auto env_config = request.Message().command_request().env().find(
+ kCuttlefishConfigEnvVarName);
+ std::string config_path;
+ if (env_config != request.Message().command_request().env().end()) {
+ config_path = env_config->second;
+ }
+ *response.mutable_status() =
+ instance_manager_.CvdFleet(request.Out(), config_path);
+ return response;
+ } else if (bin == kStartBin) {
+ auto first_instance = 1;
+ auto instance_env =
+ request.Message().command_request().env().find("CUTTLEFISH_INSTANCE");
+ if (instance_env != request.Message().command_request().env().end()) {
+ first_instance = std::stoi(instance_env->second);
+ }
+ auto ins_flag = GflagsCompatFlag("base_instance_num", first_instance);
+ auto num_instances = 1;
+ auto num_instances_flag = GflagsCompatFlag("num_instances", num_instances);
+ CF_EXPECT(ParseFlags({ins_flag, num_instances_flag}, args));
+
+ // Track this assembly_dir in the fleet.
+ InstanceManager::InstanceGroupInfo info;
+ info.host_binaries_dir = host_artifacts_path->second + "/bin/";
+ for (int i = first_instance; i < first_instance + num_instances; i++) {
+ info.instances.insert(i);
+ }
+ instance_manager_.SetInstanceGroup(home, info);
+ }
+
+ Command command("(replaced)");
+ if (bin == kFetchBin) {
+ command.SetExecutable(HostBinaryPath("fetch_cvd"));
+ } else if (bin == kMkdirBin) {
+ command.SetExecutable(kMkdirBin);
+ } else {
+ auto assembly_info = CF_EXPECT(instance_manager_.GetInstanceGroup(home));
+ command.SetExecutable(assembly_info.host_binaries_dir + bin);
+ }
+ for (const std::string& arg : args_copy) {
+ command.AddParameter(arg);
+ }
+
+ // Set CuttlefishConfig path based on assembly dir,
+ // used by subcommands when locating the CuttlefishConfig.
+ if (request.Message().command_request().env().count(
+ kCuttlefishConfigEnvVarName) == 0) {
+ auto config_path = GetCuttlefishConfigPath(home);
+ if (config_path) {
+ command.AddEnvironmentVariable(kCuttlefishConfigEnvVarName, *config_path);
+ }
+ }
+ for (auto& it : request.Message().command_request().env()) {
+ command.UnsetFromEnvironment(it.first);
+ command.AddEnvironmentVariable(it.first, it.second);
+ }
+
+ // Redirect stdin, stdout, stderr back to the cvd client
+ command.RedirectStdIO(Subprocess::StdIOChannel::kStdIn, request.In());
+ command.RedirectStdIO(Subprocess::StdIOChannel::kStdOut, request.Out());
+ command.RedirectStdIO(Subprocess::StdIOChannel::kStdErr, request.Err());
+ SubprocessOptions options;
+
+ if (request.Message().command_request().wait_behavior() ==
+ cvd::WAIT_BEHAVIOR_START) {
+ options.ExitWithParent(false);
+ }
+
+ const auto& working_dir =
+ request.Message().command_request().working_directory();
+ if (!working_dir.empty()) {
+ auto fd = SharedFD::Open(working_dir, O_RDONLY | O_PATH | O_DIRECTORY);
+ CF_EXPECT(fd->IsOpen(),
+ "Couldn't open \"" << working_dir << "\": " << fd->StrError());
+ command.SetWorkingDirectory(fd);
+ }
+
+ subprocess_ = command.Start(options);
+
+ if (request.Message().command_request().wait_behavior() ==
+ cvd::WAIT_BEHAVIOR_START) {
+ response.mutable_status()->set_code(cvd::Status::OK);
+ return response;
+ }
+ interrupt_lock.unlock();
+
+ siginfo_t infop{};
+
+ // This blocks until the process exits, but doesn't reap it.
+ auto result = subprocess_->Wait(&infop, WEXITED | WNOWAIT);
+ CF_EXPECT(result != -1, "Lost track of subprocess pid");
+ interrupt_lock.lock();
+ // Perform a reaping wait on the process (which should already have exited).
+ result = subprocess_->Wait(&infop, WEXITED);
+ CF_EXPECT(result != -1, "Lost track of subprocess pid");
+ // The double wait avoids a race around the kernel reusing pids. Waiting
+ // with WNOWAIT won't cause the child process to be reaped, so the kernel
+ // won't reuse the pid until the Wait call below, and any kill signals won't
+ // reach unexpected processes.
+
+ subprocess_ = {};
+
+ if (infop.si_code == CLD_EXITED && bin == kStopBin) {
+ instance_manager_.RemoveInstanceGroup(home);
+ }
+
+ if (infop.si_code == CLD_EXITED && infop.si_status == 0) {
+ response.mutable_status()->set_code(cvd::Status::OK);
+ return response;
+ }
+
+ response.mutable_status()->set_code(cvd::Status::INTERNAL);
+ if (infop.si_code == CLD_EXITED) {
+ response.mutable_status()->set_message("Exited with code " +
+ std::to_string(infop.si_status));
+ } else if (infop.si_code == CLD_KILLED) {
+ response.mutable_status()->set_message("Exited with signal " +
+ std::to_string(infop.si_status));
+ } else {
+ response.mutable_status()->set_message("Quit with code " +
+ std::to_string(infop.si_status));
+ }
+ return response;
+}
+
+Result<void> CvdCommandHandler::Interrupt() {
+ std::scoped_lock interrupt_lock(interruptible_);
+ if (subprocess_) {
+ auto stop_result = subprocess_->Stop();
+ switch (stop_result) {
+ case StopperResult::kStopFailure:
+ return CF_ERR("Failed to stop subprocess");
+ case StopperResult::kStopCrash:
+ return CF_ERR("Stopper caused process to crash");
+ case StopperResult::kStopSuccess:
+ return {};
+ default:
+ return CF_ERR("Unknown stop result: " << (uint64_t)stop_result);
+ }
+ }
+ return {};
+}
+
+CommandInvocation ParseInvocation(const cvd::Request& request) {
+ CommandInvocation invocation;
+ if (request.contents_case() != cvd::Request::ContentsCase::kCommandRequest) {
+ return invocation;
+ }
+ if (request.command_request().args_size() == 0) {
+ return invocation;
+ }
+ for (const std::string& arg : request.command_request().args()) {
+ invocation.arguments.push_back(arg);
+ }
+ invocation.arguments[0] = cpp_basename(invocation.arguments[0]);
+ if (invocation.arguments[0] == "cvd") {
+ if (invocation.arguments.size() == 1) {
+ // Show help if user invokes `cvd` alone.
+ invocation.command = "help";
+ invocation.arguments = {};
+ } else { // More arguments
+ invocation.command = invocation.arguments[1];
+ invocation.arguments.erase(invocation.arguments.begin());
+ invocation.arguments.erase(invocation.arguments.begin());
+ }
+ } else {
+ invocation.command = invocation.arguments[0];
+ invocation.arguments.erase(invocation.arguments.begin());
+ }
+ return invocation;
+}
+
+fruit::Component<fruit::Required<InstanceManager>> cvdCommandComponent() {
+ return fruit::createComponent()
+ .addMultibinding<CvdServerHandler, CvdCommandHandler>();
+}
+
+} // namespace cuttlefish
diff --git a/common/libs/utils/size_utils.cpp b/host/commands/cvd/server_constants.h
similarity index 61%
copy from common/libs/utils/size_utils.cpp
copy to host/commands/cvd/server_constants.h
index 9f25445..300c8c4 100644
--- a/common/libs/utils/size_utils.cpp
+++ b/host/commands/cvd/server_constants.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2018 The Android Open Source Project
+ * Copyright (C) 2021 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -14,15 +14,16 @@
* limitations under the License.
*/
-#include "common/libs/utils/size_utils.h"
-
-#include <unistd.h>
-
namespace cuttlefish {
+namespace cvd {
-uint64_t AlignToPowerOf2(uint64_t val, uint8_t align_log) {
- uint64_t align = 1ULL << align_log;
- return ((val + (align - 1)) / align) * align;
-}
+// Major version uprevs are backwards incompatible.
+// Minor version uprevs are backwards compatible within major version.
+constexpr int kVersionMajor = 1;
+constexpr int kVersionMinor = 1;
+// Pathname of the abstract cvd_server socket.
+constexpr char kServerSocketPath[] = "cvd_server";
+
+} // namespace cvd
} // namespace cuttlefish
diff --git a/host/commands/cvd/server_shutdown.cpp b/host/commands/cvd/server_shutdown.cpp
new file mode 100644
index 0000000..ec7af0b
--- /dev/null
+++ b/host/commands/cvd/server_shutdown.cpp
@@ -0,0 +1,96 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "host/commands/cvd/server.h"
+
+#include <fruit/fruit.h>
+
+#include "cvd_server.pb.h"
+
+#include "common/libs/fs/shared_buf.h"
+#include "common/libs/fs/shared_fd.h"
+#include "common/libs/utils/result.h"
+#include "host/commands/cvd/instance_manager.h"
+
+namespace cuttlefish {
+namespace {
+
+class CvdShutdownHandler : public CvdServerHandler {
+ public:
+ INJECT(CvdShutdownHandler(CvdServer& server,
+ InstanceManager& instance_manager))
+ : server_(server), instance_manager_(instance_manager) {}
+
+ Result<bool> CanHandle(const RequestWithStdio& request) const override {
+ return request.Message().contents_case() ==
+ cvd::Request::ContentsCase::kShutdownRequest;
+ }
+
+ Result<cvd::Response> Handle(const RequestWithStdio& request) override {
+ CF_EXPECT(CanHandle(request));
+ cvd::Response response;
+ response.mutable_shutdown_response();
+
+ if (!request.Extra()) {
+ response.mutable_status()->set_code(cvd::Status::FAILED_PRECONDITION);
+ response.mutable_status()->set_message(
+ "Missing extra SharedFD for shutdown");
+ return response;
+ }
+
+ if (request.Message().shutdown_request().clear()) {
+ *response.mutable_status() =
+ instance_manager_.CvdClear(request.Out(), request.Err());
+ if (response.status().code() != cvd::Status::OK) {
+ return response;
+ }
+ }
+
+ if (instance_manager_.HasInstanceGroups()) {
+ response.mutable_status()->set_code(cvd::Status::FAILED_PRECONDITION);
+ response.mutable_status()->set_message(
+ "Cannot shut down cvd_server while devices are being tracked. "
+ "Try `cvd kill-server`.");
+ return response;
+ }
+
+ // Intentionally leak the write_pipe fd so that it only closes
+ // when this process fully exits.
+ (*request.Extra())->UNMANAGED_Dup();
+
+ WriteAll(request.Out(), "Stopping the cvd_server.\n");
+ server_.Stop();
+
+ response.mutable_status()->set_code(cvd::Status::OK);
+ return response;
+ }
+
+ Result<void> Interrupt() override { return CF_ERR("Can't interrupt"); }
+
+ private:
+ CvdServer& server_;
+ InstanceManager& instance_manager_;
+};
+
+} // namespace
+
+fruit::Component<fruit::Required<CvdServer, InstanceManager>>
+cvdShutdownComponent() {
+ return fruit::createComponent()
+ .addMultibinding<CvdServerHandler, CvdShutdownHandler>();
+}
+
+} // namespace cuttlefish
diff --git a/host/commands/cvd/server_version.cpp b/host/commands/cvd/server_version.cpp
new file mode 100644
index 0000000..2168c0f
--- /dev/null
+++ b/host/commands/cvd/server_version.cpp
@@ -0,0 +1,62 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "host/commands/cvd/server.h"
+
+#include <build/version.h>
+#include <fruit/fruit.h>
+
+#include "cvd_server.pb.h"
+
+#include "common/libs/utils/result.h"
+#include "host/commands/cvd/server_constants.h"
+#include "host/libs/config/host_tools_version.h"
+
+namespace cuttlefish {
+namespace {
+
+class CvdVersionHandler : public CvdServerHandler {
+ public:
+ INJECT(CvdVersionHandler()) = default;
+
+ Result<bool> CanHandle(const RequestWithStdio& request) const override {
+ return request.Message().contents_case() ==
+ cvd::Request::ContentsCase::kVersionRequest;
+ }
+
+ Result<cvd::Response> Handle(const RequestWithStdio& request) override {
+ CF_EXPECT(CanHandle(request));
+ cvd::Response response;
+ auto& version = *response.mutable_version_response()->mutable_version();
+ version.set_major(cvd::kVersionMajor);
+ version.set_minor(cvd::kVersionMinor);
+ version.set_build(android::build::GetBuildNumber());
+ version.set_crc32(FileCrc("/proc/self/exe"));
+ response.mutable_status()->set_code(cvd::Status::OK);
+ return response;
+ }
+
+ Result<void> Interrupt() override { return CF_ERR("Can't interrupt"); }
+};
+
+} // namespace
+
+fruit::Component<> cvdVersionComponent() {
+ return fruit::createComponent()
+ .addMultibinding<CvdServerHandler, CvdVersionHandler>();
+}
+
+} // namespace cuttlefish
diff --git a/host/commands/cvd_send_sms/Android.bp b/host/commands/cvd_send_sms/Android.bp
new file mode 100644
index 0000000..739aecd
--- /dev/null
+++ b/host/commands/cvd_send_sms/Android.bp
@@ -0,0 +1,64 @@
+//
+// Copyright (C) 2022 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package {
+ default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+cc_defaults {
+ name: "cvd_send_sms_defaults",
+ shared_libs: [
+ "libbase",
+ "libcuttlefish_fs",
+ "liblog",
+ "libicuuc",
+ ],
+ defaults: ["cuttlefish_buildhost_only"],
+}
+
+cc_library_static {
+ name: "libcvd_send_sms",
+ srcs: [
+ "sms_sender.cc",
+ "pdu_format_builder.cc"
+ ],
+ defaults: ["cvd_send_sms_defaults"],
+}
+
+cc_binary {
+ name: "cvd_send_sms",
+ srcs: [
+ "main.cc"
+ ],
+ static_libs: [
+ "libcvd_send_sms",
+ "libgflags",
+ ],
+ defaults: ["cvd_send_sms_defaults"],
+}
+
+cc_test_host {
+ name: "cvd_send_sms_test",
+ srcs: [
+ "unittest/main_test.cc",
+ "unittest/sms_sender_test.cc",
+ "unittest/pdu_format_builder_test.cc",
+ ],
+ static_libs: [
+ "libgmock",
+ "libcvd_send_sms",
+ ],
+ defaults: ["cvd_send_sms_defaults"],
+}
diff --git a/host/commands/cvd_send_sms/main.cc b/host/commands/cvd_send_sms/main.cc
new file mode 100644
index 0000000..058b355
--- /dev/null
+++ b/host/commands/cvd_send_sms/main.cc
@@ -0,0 +1,48 @@
+#include "gflags/gflags.h"
+
+#include "android-base/logging.h"
+#include "common/libs/fs/shared_fd.h"
+#include "host/commands/cvd_send_sms/sms_sender.h"
+
+DEFINE_string(sender_number, "+16501234567",
+ "sender phone number in E.164 format");
+DEFINE_uint32(instance_number, 1,
+ "number of the cvd instance to send the sms to, default is 1");
+DEFINE_uint32(modem_id, 0,
+ "modem id needed for multisim devices, default is 0");
+
+namespace cuttlefish {
+namespace {
+
+// Usage examples:
+// * cvd_send_sms "hello world"
+// * cvd_send_sms --sender_number="+16501239999" "hello world"
+// * cvd_send_sms --sender_number="16501239999" "hello world"
+// * cvd_send_sms --instance-number=2 "hello world"
+// * cvd_send_sms --instance-number=2 --modem_id=1 "hello world"
+
+int SendSmsMain(int argc, char** argv) {
+ gflags::ParseCommandLineFlags(&argc, &argv, true);
+ if (argc == 1) {
+ LOG(ERROR) << "Missing message content. First positional argument is used "
+ "as the message content, `cvd_send_sms --instance-number=2 "
+ "\"hello world\"`";
+ return -1;
+ }
+ // Builds the name of the corresponding modem simulator monitor socket.
+ // https://cs.android.com/android/platform/superproject/+/master:device/google/cuttlefish/host/commands/modem_simulator/main.cpp;l=115;drc=cbfe7dba44bfea95049152b828c1a5d35c9e0522
+ std::string socket_name = std::string("modem_simulator") +
+ std::to_string(1000 + FLAGS_instance_number);
+ auto client_socket = cuttlefish::SharedFD::SocketLocalClient(
+ socket_name.c_str(), /* abstract */ true, SOCK_STREAM);
+ SmsSender sms_sender(client_socket);
+ if (!sms_sender.Send(argv[1], FLAGS_sender_number, FLAGS_modem_id)) {
+ return -1;
+ }
+ return 0;
+}
+
+} // namespace
+} // namespace cuttlefish
+
+int main(int argc, char** argv) { return cuttlefish::SendSmsMain(argc, argv); }
diff --git a/host/commands/cvd_send_sms/pdu_format_builder.cc b/host/commands/cvd_send_sms/pdu_format_builder.cc
new file mode 100644
index 0000000..943819d
--- /dev/null
+++ b/host/commands/cvd_send_sms/pdu_format_builder.cc
@@ -0,0 +1,167 @@
+#include "host/commands/cvd_send_sms/pdu_format_builder.h"
+
+#include <algorithm>
+#include <codecvt>
+#include <cstddef>
+#include <iomanip>
+#include <iostream>
+#include <map>
+#include <regex>
+#include <sstream>
+#include <vector>
+
+#include "android-base/logging.h"
+#include "unicode/uchriter.h"
+#include "unicode/unistr.h"
+#include "unicode/ustring.h"
+
+namespace cuttlefish {
+
+namespace {
+// 3GPP TS 23.038 V9.1.1 section 6.2.1 - GSM 7 bit Default Alphabet
+// https://www.etsi.org/deliver/etsi_ts/123000_123099/123038/09.01.01_60/ts_123038v090101p.pdf
+// clang-format off
+const std::vector<std::string> kGSM7BitDefaultAlphabet = {
+ "@", "£", "$", "¥", "è", "é", "ù", "ì", "ò", "Ç", "\n", "Ø", "ø", "\r", "Å", "å",
+ "Δ", "_", "Φ", "Γ", "Λ", "Ω", "Π", "Ψ", "Σ", "Θ", "Ξ", u8"\uffff" /*ESC*/, "Æ", "æ", "ß", "É",
+ " ", "!", "\"", "#", "¤", "%", "&", "'", "(", ")", "*", "+", ",", "-", ".", "/",
+ "0", "1", "2", "3", "4", "5", "6", "7", "8", "9", ":", ";", "<", "=", ">", "?",
+ "¡", "A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", "N", "O",
+ "P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z", "Ä", "Ö", "Ñ", "Ü", "§",
+ "¿", "a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m", "n", "o",
+ "p", "q", "r", "s", "t", "u", "v", "w", "x", "y", "z", "ä", "ö", "ñ", "ü", "à",
+};
+// clang-format on
+
+// Encodes using the GSM 7bit encoding as defined in 3GPP TS 23.038
+// https://www.etsi.org/deliver/etsi_ts/123000_123099/123038/09.01.01_60/ts_123038v090101p.pdf
+static std::string Gsm7bitEncode(const std::string& input) {
+ icu::UnicodeString unicode_str(input.c_str());
+ icu::UCharCharacterIterator iter(unicode_str.getTerminatedBuffer(),
+ unicode_str.length());
+ size_t octects_size = unicode_str.length() - (unicode_str.length() / 8);
+ std::byte octects[octects_size];
+ std::byte* octects_index = octects;
+ int bits_to_write_in_prev_octect = 0;
+ for (; iter.hasNext(); iter.next()) {
+ UChar uchar = iter.current();
+ char dest[5];
+ UErrorCode uerror_code;
+ u_strToUTF8(dest, 5, NULL, &uchar, 1, &uerror_code);
+ if (U_FAILURE(uerror_code)) {
+ LOG(ERROR) << "u_strToUTF8 failed with error: "
+ << u_errorName(uerror_code) << ", with string: " << input;
+ return "";
+ }
+ std::string character(dest);
+ auto found_it = std::find(kGSM7BitDefaultAlphabet.begin(),
+ kGSM7BitDefaultAlphabet.end(), character);
+ if (found_it == kGSM7BitDefaultAlphabet.end()) {
+ LOG(ERROR) << "Character: " << character
+ << " does not exist in GSM 7 bit Default Alphabet";
+ return "";
+ }
+ std::byte code =
+ (std::byte)std::distance(kGSM7BitDefaultAlphabet.begin(), found_it);
+ if (iter.hasPrevious()) {
+ std::byte prev_octect_value = *(octects_index - 1);
+ // Writes the corresponding lowest part in the previous octect.
+ *(octects_index - 1) =
+ code << (8 - bits_to_write_in_prev_octect) | prev_octect_value;
+ }
+ if (bits_to_write_in_prev_octect < 7) {
+ // Writes the remaining highest part in the current octect.
+ *octects_index = code >> bits_to_write_in_prev_octect;
+ bits_to_write_in_prev_octect++;
+ octects_index++;
+ } else { // bits_to_write_in_prev_octect == 7
+ // The 7 bits of the current character were fully packed into the
+ // previous octect.
+ bits_to_write_in_prev_octect = 0;
+ }
+ }
+ std::stringstream result;
+ for (int i = 0; i < octects_size; i++) {
+ result << std::setfill('0') << std::setw(2) << std::hex
+ << std::to_integer<int>(octects[i]);
+ }
+ return result.str();
+}
+
+// Validates whether the passed phone number conforms to the E.164 specs,
+// https://www.itu.int/rec/T-REC-E.164
+static bool IsValidE164PhoneNumber(const std::string& number) {
+ const static std::regex e164_regex("^\\+?[1-9]\\d{1,14}$");
+ return std::regex_match(number, e164_regex);
+}
+
+// Encodes numeric values by using the Semi-Octect representation.
+static std::string SemiOctectsEncode(const std::string& input) {
+ bool length_is_odd = input.length() % 2 == 1;
+ int end = length_is_odd ? input.length() - 1 : input.length();
+ std::stringstream ss;
+ for (int i = 0; i < end; i += 2) {
+ ss << input[i + 1];
+ ss << input[i];
+ }
+ if (length_is_odd) {
+ ss << "f";
+ ss << input[input.length() - 1];
+ }
+ return ss.str();
+}
+
+// Converts to hexadecimal representation filling with a leading 0 if
+// necessary.
+static std::string DecimalToHexString(int number) {
+ std::stringstream ss;
+ ss << std::setfill('0') << std::setw(2) << std::hex << number;
+ return ss.str();
+}
+} // namespace
+
+void PDUFormatBuilder::SetUserData(const std::string& user_data) {
+ user_data_ = user_data;
+}
+
+void PDUFormatBuilder::SetSenderNumber(const std::string& sender_number) {
+ sender_number_ = sender_number;
+}
+
+std::string PDUFormatBuilder::Build() {
+ if (user_data_.empty()) {
+ LOG(ERROR) << "Empty user data.";
+ return "";
+ }
+ if (sender_number_.empty()) {
+ LOG(ERROR) << "Empty sender phone number.";
+ return "";
+ }
+ if (!IsValidE164PhoneNumber(sender_number_)) {
+ LOG(ERROR) << "Sender phone number"
+ << " \"" << sender_number_ << "\" "
+ << "does not conform with the E.164 format";
+ return "";
+ }
+ std::string sender_number_without_plus =
+ sender_number_[0] == '+' ? sender_number_.substr(1) : sender_number_;
+ int ulength = icu::UnicodeString(user_data_.c_str()).length();
+ if (ulength > 160) {
+ LOG(ERROR) << "Invalid user data as it has more than 160 characters: "
+ << user_data_;
+ return "";
+ }
+ std::string encoded = Gsm7bitEncode(user_data_);
+ if (encoded.empty()) {
+ return "";
+ }
+ std::stringstream ss;
+ ss << "000100" << DecimalToHexString(sender_number_without_plus.length())
+ << "91" // 91 indicates international phone number format.
+ << SemiOctectsEncode(sender_number_without_plus)
+ << "00" // TP-PID. Protocol identifier
+ << "00" // TP-DCS. Data coding scheme. The GSM 7bit default alphabet.
+ << DecimalToHexString(ulength) << encoded;
+ return ss.str();
+}
+} // namespace cuttlefish
diff --git a/host/commands/cvd_send_sms/pdu_format_builder.h b/host/commands/cvd_send_sms/pdu_format_builder.h
new file mode 100644
index 0000000..8b5ce76
--- /dev/null
+++ b/host/commands/cvd_send_sms/pdu_format_builder.h
@@ -0,0 +1,52 @@
+//
+// Copyright (C) 2022 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#pragma once
+
+#include <string>
+
+namespace cuttlefish {
+
+// Builds PDU format strings used to send SMS to Cuttlefish modem simulator.
+//
+// PDU format is specified by the Etsi organization in GSM 03.40
+// https://www.etsi.org/deliver/etsi_gts/03/0340/05.03.00_60/gsmts_0340v050300p.pdf
+//
+// The resulting PDU format string encapsulates different parameters
+// values like:
+// * The phone number.
+// * Data coding scheme. 7 bit Alphabet or 8 bit (used in e.g. smart
+// messaging, OTA provisioning etc)
+// * User data.
+//
+// NOTE: For sender phone number, only international numbers following the
+// E.164 format (https://www.itu.int/rec/T-REC-E.164) are supported.
+//
+// NOTE: The coding scheme is not parameterized yet using always the 7bit
+// Alphabet coding scheme.
+class PDUFormatBuilder {
+ public:
+ void SetUserData(const std::string& user_data);
+ void SetSenderNumber(const std::string& number);
+ // Returns the corresponding PDU format string, returns an empty string if
+ // the User Data or the Sender Number set are invalid.
+ std::string Build();
+
+ private:
+ std::string user_data_;
+ std::string sender_number_;
+};
+
+} // namespace cuttlefish
diff --git a/host/commands/cvd_send_sms/sms_sender.cc b/host/commands/cvd_send_sms/sms_sender.cc
new file mode 100644
index 0000000..5c8b8db
--- /dev/null
+++ b/host/commands/cvd_send_sms/sms_sender.cc
@@ -0,0 +1,44 @@
+#include "host/commands/cvd_send_sms/sms_sender.h"
+
+#include <algorithm>
+#include <codecvt>
+#include <cstddef>
+#include <iomanip>
+#include <iostream>
+#include <map>
+#include <vector>
+
+#include "android-base/logging.h"
+#include "common/libs/fs/shared_buf.h"
+#include "host/commands/cvd_send_sms/pdu_format_builder.h"
+
+namespace cuttlefish {
+
+SmsSender::SmsSender(SharedFD modem_simulator_client_fd)
+ : modem_simulator_client_fd_(modem_simulator_client_fd) {}
+
+bool SmsSender::Send(const std::string& content,
+ const std::string& sender_number, uint32_t modem_id) {
+ if (!modem_simulator_client_fd_->IsOpen()) {
+ LOG(ERROR) << "Failed to connect to remote modem simulator, error: "
+ << modem_simulator_client_fd_->StrError();
+ return false;
+ }
+ PDUFormatBuilder builder;
+ builder.SetUserData(content);
+ builder.SetSenderNumber(sender_number);
+ std::string pdu_format_str = builder.Build();
+ if (pdu_format_str.empty()) {
+ return false;
+ }
+ // https://cs.android.com/android/platform/superproject/+/master:device/google/cuttlefish/host/commands/modem_simulator/main.cpp;l=151;drc=cbfe7dba44bfea95049152b828c1a5d35c9e0522
+ std::string at_command = "REM" + std::to_string(modem_id) +
+ "AT+REMOTESMS=" + pdu_format_str + "\r";
+ if (WriteAll(modem_simulator_client_fd_, at_command) != at_command.size()) {
+ LOG(ERROR) << "Error writing to socket: "
+ << modem_simulator_client_fd_->StrError();
+ return false;
+ }
+ return true;
+}
+} // namespace cuttlefish
diff --git a/host/commands/cvd_send_sms/sms_sender.h b/host/commands/cvd_send_sms/sms_sender.h
new file mode 100644
index 0000000..102d636
--- /dev/null
+++ b/host/commands/cvd_send_sms/sms_sender.h
@@ -0,0 +1,35 @@
+//
+// Copyright (C) 2022 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#pragma once
+
+#include <string>
+
+#include "common/libs/fs/shared_fd.h"
+
+namespace cuttlefish {
+
+class SmsSender {
+ public:
+ SmsSender(SharedFD modem_simulator_client_fd);
+
+ // Returns true if SMS was successfully sent, returns false otherwise.
+ bool Send(const std::string& sms_body, const std::string& sender_number,
+ uint32_t modem_id = 0);
+
+ private:
+ SharedFD modem_simulator_client_fd_;
+};
+} // namespace cuttlefish
diff --git a/host/commands/tapsetiff/Android.bp b/host/commands/cvd_send_sms/unittest/main_test.cc
similarity index 74%
rename from host/commands/tapsetiff/Android.bp
rename to host/commands/cvd_send_sms/unittest/main_test.cc
index 1d7dedb..d2ceeb7 100644
--- a/host/commands/tapsetiff/Android.bp
+++ b/host/commands/cvd_send_sms/unittest/main_test.cc
@@ -1,5 +1,5 @@
//
-// Copyright (C) 2020 The Android Open Source Project
+// Copyright (C) 2022 The Android Open Source Project
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
@@ -13,11 +13,9 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-package {
- default_applicable_licenses: ["Android-Apache-2.0"],
-}
+#include <gtest/gtest.h>
-sh_binary_host {
- name: "tapsetiff",
- src: "tapsetiff.py",
+int main(int argc, char** argv) {
+ ::testing::InitGoogleTest(&argc, argv);
+ return RUN_ALL_TESTS();
}
diff --git a/host/commands/cvd_send_sms/unittest/pdu_format_builder_test.cc b/host/commands/cvd_send_sms/unittest/pdu_format_builder_test.cc
new file mode 100644
index 0000000..6d62c0b
--- /dev/null
+++ b/host/commands/cvd_send_sms/unittest/pdu_format_builder_test.cc
@@ -0,0 +1,223 @@
+//
+// Copyright (C) 2022 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "host/commands/cvd_send_sms/pdu_format_builder.h"
+
+#include <gtest/gtest.h>
+
+namespace cuttlefish {
+namespace {
+
+TEST(PDUFormatBuilderTest, EmptyUserDataFails) {
+ PDUFormatBuilder builder;
+
+ std::string result = builder.Build();
+
+ EXPECT_EQ(result, "");
+}
+
+TEST(PDUFormatBuilderTest, NotInAlphabetCharacterFails) {
+ PDUFormatBuilder builder;
+ builder.SetUserData("ccccccc☺");
+ builder.SetSenderNumber("+16501234567");
+
+ std::string result = builder.Build();
+
+ EXPECT_EQ(result, "");
+}
+
+TEST(PDUFormatBuilderTest, With161CharactersFails) {
+ PDUFormatBuilder builder;
+ builder.SetUserData(
+ "cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc"
+ "cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc"
+ "ccccccccccccccccccccccccccccccccccccccccc");
+ builder.SetSenderNumber("+16501234567");
+
+ std::string result = builder.Build();
+
+ EXPECT_EQ(result, "");
+}
+
+TEST(PDUFormatBuilderTest, With1CharacterSucceeds) {
+ PDUFormatBuilder builder;
+ builder.SetUserData("c");
+ builder.SetSenderNumber("+16501234567");
+
+ std::string result = builder.Build();
+
+ EXPECT_EQ(result, "0001000b916105214365f700000163");
+}
+
+TEST(PDUFormatBuilderTest, With7CharactersSucceeds) {
+ PDUFormatBuilder builder;
+ builder.SetUserData("ccccccc");
+ builder.SetSenderNumber("+16501234567");
+
+ std::string result = builder.Build();
+
+ EXPECT_EQ(result, "0001000b916105214365f7000007e3f1783c1e8f01");
+}
+
+TEST(PDUFormatBuilderTest, With8CharactersSucceeds) {
+ PDUFormatBuilder builder;
+ builder.SetUserData("cccccccc");
+ builder.SetSenderNumber("+16501234567");
+
+ std::string result = builder.Build();
+
+ EXPECT_EQ(result, "0001000b916105214365f7000008e3f1783c1e8fc7");
+}
+
+TEST(PDUFormatBuilderTest, With160CharactersSucceeds) {
+ PDUFormatBuilder builder;
+ builder.SetUserData(
+ "cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc"
+ "cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc"
+ "cccccccccccccccccccccccccccccccccccccccc");
+ builder.SetSenderNumber("+16501234567");
+
+ std::string result = builder.Build();
+
+ EXPECT_EQ(result,
+ "0001000b916105214365f70000a0"
+ "e3f1783c1e8fc7"
+ "e3f1783c1e8fc7"
+ "e3f1783c1e8fc7"
+ "e3f1783c1e8fc7"
+ "e3f1783c1e8fc7"
+ "e3f1783c1e8fc7"
+ "e3f1783c1e8fc7"
+ "e3f1783c1e8fc7"
+ "e3f1783c1e8fc7"
+ "e3f1783c1e8fc7"
+ "e3f1783c1e8fc7"
+ "e3f1783c1e8fc7"
+ "e3f1783c1e8fc7"
+ "e3f1783c1e8fc7"
+ "e3f1783c1e8fc7"
+ "e3f1783c1e8fc7"
+ "e3f1783c1e8fc7"
+ "e3f1783c1e8fc7"
+ "e3f1783c1e8fc7"
+ "e3f1783c1e8fc7");
+}
+
+TEST(PDUFormatBuilderTest, With160MultiByteCharactersSucceeds) {
+ PDUFormatBuilder builder;
+ builder.SetUserData(
+ "ΩΩΩΩΩΩΩΩΩΩΩΩΩΩΩΩΩΩΩΩΩΩΩΩΩΩΩΩΩΩΩΩΩΩΩΩΩΩΩΩΩΩΩΩΩΩΩΩΩΩΩΩΩΩΩΩΩΩΩΩ"
+ "ΩΩΩΩΩΩΩΩΩΩΩΩΩΩΩΩΩΩΩΩΩΩΩΩΩΩΩΩΩΩΩΩΩΩΩΩΩΩΩΩΩΩΩΩΩΩΩΩΩΩΩΩΩΩΩΩΩΩΩΩ"
+ "ΩΩΩΩΩΩΩΩΩΩΩΩΩΩΩΩΩΩΩΩΩΩΩΩΩΩΩΩΩΩΩΩΩΩΩΩΩΩΩΩ");
+ builder.SetSenderNumber("+16501234567");
+
+ std::string result = builder.Build();
+
+ EXPECT_EQ(result,
+ "0001000b916105214365f70000a0"
+ "954aa552a9542a"
+ "954aa552a9542a"
+ "954aa552a9542a"
+ "954aa552a9542a"
+ "954aa552a9542a"
+ "954aa552a9542a"
+ "954aa552a9542a"
+ "954aa552a9542a"
+ "954aa552a9542a"
+ "954aa552a9542a"
+ "954aa552a9542a"
+ "954aa552a9542a"
+ "954aa552a9542a"
+ "954aa552a9542a"
+ "954aa552a9542a"
+ "954aa552a9542a"
+ "954aa552a9542a"
+ "954aa552a9542a"
+ "954aa552a9542a"
+ "954aa552a9542a");
+}
+
+TEST(PDUFormatBuilderTest, FullAlphabetSucceeds) {
+ PDUFormatBuilder builder;
+ builder.SetUserData(
+ "@£$¥èéùìòÇ\nØø\rÅåΔ_ΦΓΛΩΠΨΣΘΞ\uffffÆæßÉ "
+ "!\"#¤%&'()*+,-./"
+ "0123456789:;<=>?"
+ "¡ABCDEFGHIJKLMNOPQRSTUVWXYZÄÖÑܧ¿abcdefghijklmnopqrstuvwxyzäöñüà");
+ builder.SetSenderNumber("+16501234567");
+
+ std::string result = builder.Build();
+
+ EXPECT_EQ(
+ result,
+ "0001000b916105214365f70000808080604028180e888462c168381e90886442a9582e98"
+ "8c66c3e9783ea09068442a994ea8946ac56ab95eb0986c46abd96eb89c6ec7ebf97ec0a0"
+ "70482c1a8fc8a472c96c3a9fd0a8744aad5aafd8ac76cbed7abfe0b0784c2e9bcfe8b47a"
+ "cd6ebbdff0b87c4eafdbeff8bc7ecfeffbff");
+}
+
+TEST(PDUFormatBuilderTest, WithEmptySenderPhoneNumberFails) {
+ PDUFormatBuilder builder;
+ builder.SetUserData("c");
+ builder.SetSenderNumber("");
+
+ std::string result = builder.Build();
+
+ EXPECT_EQ(result, "");
+}
+
+TEST(PDUFormatBuilderTest, WithInvalidSenderPhoneNumberFails) {
+ std::vector<std::string> numbers{"06501234567", "1", "1650603619399999"};
+ PDUFormatBuilder builder;
+ builder.SetUserData("c");
+
+ for (auto n : numbers) {
+ builder.SetSenderNumber(n);
+ EXPECT_EQ(builder.Build(), "");
+ }
+}
+
+TEST(PDUFormatBuilderTest, WithoutLeadingPlusSignSucceeds) {
+ PDUFormatBuilder builder;
+ builder.SetUserData("c");
+ builder.SetSenderNumber("16501234567");
+
+ std::string result = builder.Build();
+
+ EXPECT_EQ(result, "0001000b916105214365f700000163");
+}
+
+TEST(PDUFormatBuilderTest, WithOddSenderPhoneNumberLengthSucceeds) {
+ PDUFormatBuilder builder;
+ builder.SetUserData("c");
+ builder.SetSenderNumber("+16501234567");
+
+ std::string result = builder.Build();
+
+ EXPECT_EQ(result, "0001000b916105214365f700000163");
+}
+
+TEST(PDUFormatBuilderTest, WithEvenSenderPhoneNumberLengthSucceeds) {
+ PDUFormatBuilder builder;
+ builder.SetUserData("c");
+ builder.SetSenderNumber("+526501234567");
+
+ std::string result = builder.Build();
+
+ EXPECT_EQ(result, "0001000c9125561032547600000163");
+}
+
+} // namespace
+} // namespace cuttlefish
diff --git a/host/commands/cvd_send_sms/unittest/sms_sender_test.cc b/host/commands/cvd_send_sms/unittest/sms_sender_test.cc
new file mode 100644
index 0000000..e2dce44
--- /dev/null
+++ b/host/commands/cvd_send_sms/unittest/sms_sender_test.cc
@@ -0,0 +1,84 @@
+//
+// Copyright (C) 2022 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "host/commands/cvd_send_sms/sms_sender.h"
+
+#include <sstream>
+
+#include <android-base/logging.h>
+#include <gmock/gmock.h>
+#include <gtest/gtest.h>
+
+#include "common/libs/fs/shared_fd.h"
+
+namespace cuttlefish {
+namespace {
+
+class SmsSenderTest : public ::testing::Test {
+ protected:
+ void SetUp() override {
+ CHECK(SharedFD::SocketPair(AF_LOCAL, SOCK_STREAM, 0, &client_fd_,
+ &fake_server_fd_))
+ << strerror(errno);
+ CHECK(client_fd_->IsOpen());
+ CHECK(fake_server_fd_->IsOpen());
+ }
+
+ void AssertCommandIsSent(std::string expected_command) {
+ std::stringstream ss;
+ std::vector<char> buffer(4096);
+ ssize_t bytes_read;
+ do {
+ bytes_read = fake_server_fd_->Read(buffer.data(), buffer.size());
+ CHECK(bytes_read >= 0) << strerror(errno);
+ ss << std::string(buffer.data(), bytes_read);
+ } while (buffer[bytes_read - 1] != '\r');
+ EXPECT_THAT(ss.str(), testing::Eq(expected_command));
+ }
+
+ SharedFD client_fd_;
+ SharedFD fake_server_fd_;
+};
+
+TEST_F(SmsSenderTest, InvalidContentFails) {
+ SmsSender sender(client_fd_);
+
+ bool result = sender.Send("", "+16501234567");
+
+ EXPECT_FALSE(result);
+}
+
+TEST_F(SmsSenderTest, ValidContentSucceeds) {
+ SmsSender sender(client_fd_);
+
+ bool result = sender.Send("hellohello", "+16501234567");
+
+ EXPECT_TRUE(result);
+ AssertCommandIsSent(
+ "REM0AT+REMOTESMS=0001000b916105214365f700000ae8329bfd4697d9ec37\r");
+}
+
+TEST_F(SmsSenderTest, NonDefaultModemIdValueSucceeds) {
+ SmsSender sender(client_fd_);
+
+ bool result = sender.Send("hellohello", "+16501234567", 1);
+
+ EXPECT_TRUE(result);
+ AssertCommandIsSent(
+ "REM1AT+REMOTESMS=0001000b916105214365f700000ae8329bfd4697d9ec37\r");
+}
+
+} // namespace
+} // namespace cuttlefish
diff --git a/host/commands/cvd_status/Android.bp b/host/commands/cvd_status/Android.bp
deleted file mode 100644
index ae48d19..0000000
--- a/host/commands/cvd_status/Android.bp
+++ /dev/null
@@ -1,37 +0,0 @@
-//
-// Copyright (C) 2018 The Android Open Source Project
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package {
- default_applicable_licenses: ["Android-Apache-2.0"],
-}
-
-cc_binary {
- name: "cvd_status",
- srcs: [
- "cvd_status.cc",
- ],
- shared_libs: [
- "libbase",
- "libcuttlefish_fs",
- "libcuttlefish_utils",
- "libjsoncpp",
- ],
- static_libs: [
- "libcuttlefish_host_config",
- "libcuttlefish_vm_manager",
- "libgflags",
- ],
- defaults: ["cuttlefish_host", "cuttlefish_libicuuc"],
-}
diff --git a/host/commands/cvd_status/cvd_status.cc b/host/commands/cvd_status/cvd_status.cc
deleted file mode 100644
index 81436d4..0000000
--- a/host/commands/cvd_status/cvd_status.cc
+++ /dev/null
@@ -1,111 +0,0 @@
-/*
- * Copyright (C) 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#include <inttypes.h>
-#include <limits.h>
-#include <stdio.h>
-#include <stdint.h>
-#include <stdlib.h>
-#include <sys/types.h>
-#include <sys/stat.h>
-#include <sys/wait.h>
-#include <fcntl.h>
-#include <unistd.h>
-#include <signal.h>
-
-#include <algorithm>
-#include <cstdlib>
-#include <fstream>
-#include <iomanip>
-#include <memory>
-#include <sstream>
-#include <string>
-#include <vector>
-
-#include <gflags/gflags.h>
-#include <android-base/logging.h>
-
-#include "common/libs/fs/shared_fd.h"
-#include "common/libs/fs/shared_select.h"
-#include "common/libs/utils/environment.h"
-#include "host/commands/run_cvd/runner_defs.h"
-#include "host/libs/config/cuttlefish_config.h"
-#include "host/libs/vm_manager/vm_manager.h"
-
-DEFINE_int32(wait_for_launcher, 5,
- "How many seconds to wait for the launcher to respond to the status "
- "command. A value of zero means wait indefinetly");
-
-int main(int argc, char** argv) {
- ::android::base::InitLogging(argv, android::base::StderrLogger);
- google::ParseCommandLineFlags(&argc, &argv, true);
-
- auto config = cuttlefish::CuttlefishConfig::Get();
- if (!config) {
- LOG(ERROR) << "Failed to obtain config object";
- return 1;
- }
-
- auto instance = config->ForDefaultInstance();
- auto monitor_path = instance.launcher_monitor_socket_path();
- if (monitor_path.empty()) {
- LOG(ERROR) << "No path to launcher monitor found";
- return 2;
- }
- auto monitor_socket = cuttlefish::SharedFD::SocketLocalClient(
- monitor_path.c_str(), false, SOCK_STREAM, FLAGS_wait_for_launcher);
- if (!monitor_socket->IsOpen()) {
- LOG(ERROR) << "Unable to connect to launcher monitor at " << monitor_path
- << ": " << monitor_socket->StrError();
- return 3;
- }
- auto request = cuttlefish::LauncherAction::kStatus;
- auto bytes_sent = monitor_socket->Send(&request, sizeof(request), 0);
- if (bytes_sent < 0) {
- LOG(ERROR) << "Error sending launcher monitor the status command: "
- << monitor_socket->StrError();
- return 4;
- }
- // Perform a select with a timeout to guard against launcher hanging
- cuttlefish::SharedFDSet read_set;
- read_set.Set(monitor_socket);
- struct timeval timeout = {FLAGS_wait_for_launcher, 0};
- int selected = cuttlefish::Select(&read_set, nullptr, nullptr,
- FLAGS_wait_for_launcher <= 0 ? nullptr : &timeout);
- if (selected < 0){
- LOG(ERROR) << "Failed communication with the launcher monitor: "
- << strerror(errno);
- return 5;
- }
- if (selected == 0) {
- LOG(ERROR) << "Timeout expired waiting for launcher monitor to respond";
- return 6;
- }
- cuttlefish::LauncherResponse response;
- auto bytes_recv = monitor_socket->Recv(&response, sizeof(response), 0);
- if (bytes_recv < 0) {
- LOG(ERROR) << "Error receiving response from launcher monitor: "
- << monitor_socket->StrError();
- return 7;
- }
- if (response != cuttlefish::LauncherResponse::kSuccess) {
- LOG(ERROR) << "Received '" << static_cast<char>(response)
- << "' response from launcher monitor";
- return 8;
- }
- LOG(INFO) << "run_cvd is active.";
- return 0;
-}
diff --git a/host/commands/fetcher/Android.bp b/host/commands/fetcher/Android.bp
index fdb97ec..6555a74 100644
--- a/host/commands/fetcher/Android.bp
+++ b/host/commands/fetcher/Android.bp
@@ -20,15 +20,13 @@
cc_binary {
name: "fetch_cvd",
srcs: [
- "build_api.cc",
- "credential_source.cc",
- "curl_wrapper.cc",
"fetch_cvd.cc",
- "install_zip.cc",
],
static_libs: [
+ "libcuttlefish_web",
"libcuttlefish_host_config",
"libgflags",
+ "libext2_blkid",
],
target: {
host: {
diff --git a/host/commands/fetcher/build_api.cc b/host/commands/fetcher/build_api.cc
deleted file mode 100644
index 16f95db..0000000
--- a/host/commands/fetcher/build_api.cc
+++ /dev/null
@@ -1,256 +0,0 @@
-//
-// Copyright (C) 2019 The Android Open Source Project
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include "build_api.h"
-
-#include <dirent.h>
-#include <unistd.h>
-
-#include <chrono>
-#include <set>
-#include <string>
-#include <thread>
-
-#include <android-base/strings.h>
-#include <android-base/logging.h>
-
-#include "common/libs/utils/environment.h"
-#include "common/libs/utils/files.h"
-
-namespace cuttlefish {
-namespace {
-
-const std::string BUILD_API =
- "https://www.googleapis.com/android/internal/build/v3";
-
-bool StatusIsTerminal(const std::string& status) {
- const static std::set<std::string> terminal_statuses = {
- "abandoned",
- "complete",
- "error",
- "ABANDONED",
- "COMPLETE",
- "ERROR",
- };
- return terminal_statuses.count(status) > 0;
-}
-
-} // namespace
-
-Artifact::Artifact(const Json::Value& json_artifact) {
- name = json_artifact["name"].asString();
- size = std::stol(json_artifact["size"].asString());
- last_modified_time = std::stol(json_artifact["lastModifiedTime"].asString());
- md5 = json_artifact["md5"].asString();
- content_type = json_artifact["contentType"].asString();
- revision = json_artifact["revision"].asString();
- creation_time = std::stol(json_artifact["creationTime"].asString());
- crc32 = json_artifact["crc32"].asUInt();
-}
-
-std::ostream& operator<<(std::ostream& out, const DeviceBuild& build) {
- return out << "(id=\"" << build.id << "\", target=\"" << build.target << "\")";
-}
-
-std::ostream& operator<<(std::ostream& out, const DirectoryBuild& build) {
- auto paths = android::base::Join(build.paths, ":");
- return out << "(paths=\"" << paths << "\", target=\"" << build.target << "\")";
-}
-
-std::ostream& operator<<(std::ostream& out, const Build& build) {
- std::visit([&out](auto&& arg) { out << arg; }, build);
- return out;
-}
-
-DirectoryBuild::DirectoryBuild(const std::vector<std::string>& paths,
- const std::string& target)
- : paths(paths), target(target), id("eng") {
- product = StringFromEnv("TARGET_PRODUCT", "");
-}
-
-BuildApi::BuildApi(std::unique_ptr<CredentialSource> credential_source)
- : credential_source(std::move(credential_source)) {}
-
-std::vector<std::string> BuildApi::Headers() {
- std::vector<std::string> headers;
- if (credential_source) {
- headers.push_back("Authorization:Bearer " + credential_source->Credential());
- }
- return headers;
-}
-
-std::string BuildApi::LatestBuildId(const std::string& branch,
- const std::string& target) {
- std::string url = BUILD_API + "/builds?branch=" + branch
- + "&buildAttemptStatus=complete"
- + "&buildType=submitted&maxResults=1&successful=true&target=" + target;
- auto response = curl.DownloadToJson(url, Headers());
- CHECK(!response.isMember("error")) << "Error fetching the latest build of \""
- << target << "\" on \"" << branch << "\". Response was " << response;
-
- if (!response.isMember("builds") || response["builds"].size() != 1) {
- LOG(WARNING) << "expected to receive 1 build for \"" << target << "\" on \""
- << branch << "\", but received " << response["builds"].size()
- << ". Full response was " << response;
- return "";
- }
- return response["builds"][0]["buildId"].asString();
-}
-
-std::string BuildApi::BuildStatus(const DeviceBuild& build) {
- std::string url = BUILD_API + "/builds/" + build.id + "/" + build.target;
- auto response_json = curl.DownloadToJson(url, Headers());
- CHECK(!response_json.isMember("error")) << "Error fetching the status of "
- << "build " << build << ". Response was " << response_json;
-
- return response_json["buildAttemptStatus"].asString();
-}
-
-std::string BuildApi::ProductName(const DeviceBuild& build) {
- std::string url = BUILD_API + "/builds/" + build.id + "/" + build.target;
- auto response_json = curl.DownloadToJson(url, Headers());
- CHECK(!response_json.isMember("error")) << "Error fetching the status of "
- << "build " << build << ". Response was " << response_json;
- CHECK(response_json.isMember("target")) << "Build was missing target field.";
- return response_json["target"]["product"].asString();
-}
-
-std::vector<Artifact> BuildApi::Artifacts(const DeviceBuild& build) {
- std::string page_token = "";
- std::vector<Artifact> artifacts;
- do {
- std::string url = BUILD_API + "/builds/" + build.id + "/" + build.target +
- "/attempts/latest/artifacts?maxResults=1000";
- if (page_token != "") {
- url += "&pageToken=" + page_token;
- }
- auto artifacts_json = curl.DownloadToJson(url, Headers());
- CHECK(!artifacts_json.isMember("error"))
- << "Error fetching the artifacts of " << build << ". Response was "
- << artifacts_json;
- if (artifacts_json.isMember("nextPageToken")) {
- page_token = artifacts_json["nextPageToken"].asString();
- } else {
- page_token = "";
- }
- for (const auto& artifact_json : artifacts_json["artifacts"]) {
- artifacts.emplace_back(artifact_json);
- }
- } while (page_token != "");
- return artifacts;
-}
-
-struct CloseDir {
- void operator()(DIR* dir) {
- closedir(dir);
- }
-};
-
-using UniqueDir = std::unique_ptr<DIR, CloseDir>;
-
-std::vector<Artifact> BuildApi::Artifacts(const DirectoryBuild& build) {
- std::vector<Artifact> artifacts;
- for (const auto& path : build.paths) {
- auto dir = UniqueDir(opendir(path.c_str()));
- CHECK(dir != nullptr) << "Could not read files from \"" << path << "\"";
- for (auto entity = readdir(dir.get()); entity != nullptr; entity = readdir(dir.get())) {
- artifacts.emplace_back(std::string(entity->d_name));
- }
- }
- return artifacts;
-}
-
-bool BuildApi::ArtifactToFile(const DeviceBuild& build,
- const std::string& artifact,
- const std::string& path) {
- std::string download_url_endpoint =
- BUILD_API + "/builds/" + build.id + "/" + build.target +
- "/attempts/latest/artifacts/" + artifact + "/url";
- auto download_url_json =
- curl.DownloadToJson(download_url_endpoint, Headers());
- if (!download_url_json.isMember("signedUrl")) {
- LOG(ERROR) << "URL endpoint did not have json path: " << download_url_json;
- return false;
- }
- std::string url = download_url_json["signedUrl"].asString();
- return curl.DownloadToFile(url, path);
-}
-
-bool BuildApi::ArtifactToFile(const DirectoryBuild& build,
- const std::string& artifact,
- const std::string& destination) {
- for (const auto& path : build.paths) {
- auto source = path + "/" + artifact;
- if (!FileExists(source)) {
- continue;
- }
- unlink(destination.c_str());
- if (symlink(source.c_str(), destination.c_str())) {
- int error_num = errno;
- LOG(ERROR) << "Could not create symlink from " << source << " to "
- << destination << ": " << strerror(error_num);
- return false;
- }
- return true;
- }
- return false;
-}
-
-Build ArgumentToBuild(BuildApi* build_api, const std::string& arg,
- const std::string& default_build_target,
- const std::chrono::seconds& retry_period) {
- if (arg.find(':') != std::string::npos) {
- std::vector<std::string> dirs = android::base::Split(arg, ":");
- std::string id = dirs.back();
- dirs.pop_back();
- return DirectoryBuild(dirs, id);
- }
- size_t slash_pos = arg.find('/');
- if (slash_pos != std::string::npos
- && arg.find('/', slash_pos + 1) != std::string::npos) {
- LOG(FATAL) << "Build argument cannot have more than one '/' slash. Was at "
- << slash_pos << " and " << arg.find('/', slash_pos + 1);
- }
- std::string build_target = slash_pos == std::string::npos
- ? default_build_target : arg.substr(slash_pos + 1);
- std::string branch_or_id = slash_pos == std::string::npos
- ? arg: arg.substr(0, slash_pos);
- std::string branch_latest_build_id =
- build_api->LatestBuildId(branch_or_id, build_target);
- std::string build_id = branch_or_id;
- if (branch_latest_build_id != "") {
- LOG(INFO) << "The latest good build on branch \"" << branch_or_id
- << "\"with build target \"" << build_target
- << "\" is \"" << branch_latest_build_id << "\"";
- build_id = branch_latest_build_id;
- }
- DeviceBuild proposed_build = DeviceBuild(build_id, build_target);
- std::string status = build_api->BuildStatus(proposed_build);
- if (status == "") {
- LOG(FATAL) << proposed_build << " is not a valid branch or build id.";
- }
- LOG(INFO) << "Status for build " << proposed_build << " is " << status;
- while (retry_period != std::chrono::seconds::zero() && !StatusIsTerminal(status)) {
- LOG(INFO) << "Status is \"" << status << "\". Waiting for " << retry_period.count()
- << " seconds.";
- std::this_thread::sleep_for(retry_period);
- status = build_api->BuildStatus(proposed_build);
- }
- LOG(INFO) << "Status for build " << proposed_build << " is " << status;
- proposed_build.product = build_api->ProductName(proposed_build);
- return proposed_build;
-}
-
-} // namespace cuttlefish
diff --git a/host/commands/fetcher/credential_source.cc b/host/commands/fetcher/credential_source.cc
deleted file mode 100644
index 0144e2d..0000000
--- a/host/commands/fetcher/credential_source.cc
+++ /dev/null
@@ -1,77 +0,0 @@
-//
-// Copyright (C) 2019 The Android Open Source Project
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include "credential_source.h"
-
-#include <android-base/logging.h>
-
-namespace cuttlefish {
-namespace {
-
-std::chrono::steady_clock::duration REFRESH_WINDOW =
- std::chrono::minutes(2);
-std::string REFRESH_URL = "http://metadata.google.internal/computeMetadata/"
- "v1/instance/service-accounts/default/token";
-
-} // namespace
-
-GceMetadataCredentialSource::GceMetadataCredentialSource() {
- latest_credential = "";
- expiration = std::chrono::steady_clock::now();
-}
-
-std::string GceMetadataCredentialSource::Credential() {
- if (expiration - std::chrono::steady_clock::now() < REFRESH_WINDOW) {
- RefreshCredential();
- }
- return latest_credential;
-}
-
-void GceMetadataCredentialSource::RefreshCredential() {
- Json::Value credential_json =
- curl.DownloadToJson(REFRESH_URL, {"Metadata-Flavor: Google"});
-
- CHECK(!credential_json.isMember("error")) << "Error fetching credentials. " <<
- "Response was " << credential_json;
- bool has_access_token = credential_json.isMember("access_token");
- bool has_expires_in = credential_json.isMember("expires_in");
- if (!has_access_token || !has_expires_in) {
- LOG(FATAL) << "GCE credential was missing access_token or expires_in. "
- << "Full response was " << credential_json << "";
- }
-
- expiration = std::chrono::steady_clock::now()
- + std::chrono::seconds(credential_json["expires_in"].asInt());
- latest_credential = credential_json["access_token"].asString();
-}
-
-std::unique_ptr<CredentialSource> GceMetadataCredentialSource::make() {
- return std::unique_ptr<CredentialSource>(new GceMetadataCredentialSource());
-}
-
-FixedCredentialSource::FixedCredentialSource(const std::string& credential) {
- this->credential = credential;
-}
-
-std::string FixedCredentialSource::Credential() {
- return credential;
-}
-
-std::unique_ptr<CredentialSource> FixedCredentialSource::make(
- const std::string& credential) {
- return std::unique_ptr<CredentialSource>(new FixedCredentialSource(credential));
-}
-
-} // namespace cuttlefish
diff --git a/host/commands/fetcher/credential_source.h b/host/commands/fetcher/credential_source.h
deleted file mode 100644
index 78ec51a..0000000
--- a/host/commands/fetcher/credential_source.h
+++ /dev/null
@@ -1,56 +0,0 @@
-//
-// Copyright (C) 2019 The Android Open Source Project
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#pragma once
-
-#include <chrono>
-#include <memory>
-
-#include "curl_wrapper.h"
-
-namespace cuttlefish {
-
-class CredentialSource {
-public:
- virtual ~CredentialSource() = default;
- virtual std::string Credential() = 0;
-};
-
-class GceMetadataCredentialSource : public CredentialSource {
- CurlWrapper curl;
- std::string latest_credential;
- std::chrono::steady_clock::time_point expiration;
-
- void RefreshCredential();
-public:
- GceMetadataCredentialSource();
- GceMetadataCredentialSource(GceMetadataCredentialSource&&) = default;
-
- virtual std::string Credential();
-
- static std::unique_ptr<CredentialSource> make();
-};
-
-class FixedCredentialSource : public CredentialSource {
- std::string credential;
-public:
- FixedCredentialSource(const std::string& credential);
-
- virtual std::string Credential();
-
- static std::unique_ptr<CredentialSource> make(const std::string& credential);
-};
-
-}
diff --git a/host/commands/fetcher/curl_wrapper.cc b/host/commands/fetcher/curl_wrapper.cc
deleted file mode 100644
index c32f4ce..0000000
--- a/host/commands/fetcher/curl_wrapper.cc
+++ /dev/null
@@ -1,161 +0,0 @@
-//
-// Copyright (C) 2019 The Android Open Source Project
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include "curl_wrapper.h"
-
-#include <sstream>
-#include <string>
-#include <stdio.h>
-
-#include <android-base/logging.h>
-
-#include <curl/curl.h>
-#include <json/json.h>
-
-namespace cuttlefish {
-namespace {
-
-size_t file_write_callback(char *ptr, size_t, size_t nmemb, void *userdata) {
- std::stringstream* stream = (std::stringstream*) userdata;
- stream->write(ptr, nmemb);
- return nmemb;
-}
-
-curl_slist* build_slist(const std::vector<std::string>& strings) {
- curl_slist* curl_headers = nullptr;
- for (const auto& str : strings) {
- curl_slist* temp = curl_slist_append(curl_headers, str.c_str());
- if (temp == nullptr) {
- LOG(ERROR) << "curl_slist_append failed to add " << str;
- if (curl_headers) {
- curl_slist_free_all(curl_headers);
- return nullptr;
- }
- }
- curl_headers = temp;
- }
- return curl_headers;
-}
-
-} // namespace
-
-CurlWrapper::CurlWrapper() {
- curl = curl_easy_init();
- if (!curl) {
- LOG(ERROR) << "failed to initialize curl";
- return;
- }
-}
-
-CurlWrapper::~CurlWrapper() {
- curl_easy_cleanup(curl);
-}
-
-bool CurlWrapper::DownloadToFile(const std::string& url, const std::string& path) {
- return CurlWrapper::DownloadToFile(url, path, {});
-}
-
-bool CurlWrapper::DownloadToFile(const std::string& url, const std::string& path,
- const std::vector<std::string>& headers) {
- LOG(INFO) << "Attempting to save \"" << url << "\" to \"" << path << "\"";
- if (!curl) {
- LOG(ERROR) << "curl was not initialized\n";
- return false;
- }
- curl_slist* curl_headers = build_slist(headers);
- curl_easy_reset(curl);
- curl_easy_setopt(curl, CURLOPT_CAINFO, "/etc/ssl/certs/ca-certificates.crt");
- curl_easy_setopt(curl, CURLOPT_HTTPHEADER, curl_headers);
- curl_easy_setopt(curl, CURLOPT_URL, url.c_str());
- char error_buf[CURL_ERROR_SIZE];
- curl_easy_setopt(curl, CURLOPT_ERRORBUFFER, error_buf);
- curl_easy_setopt(curl, CURLOPT_VERBOSE, 1L);
- FILE* file = fopen(path.c_str(), "w");
- if (!file) {
- LOG(ERROR) << "could not open file " << path;
- return false;
- }
- curl_easy_setopt(curl, CURLOPT_WRITEDATA, (void*) file);
- CURLcode res = curl_easy_perform(curl);
- if (curl_headers) {
- curl_slist_free_all(curl_headers);
- }
- fclose(file);
- if (res != CURLE_OK) {
- LOG(ERROR) << "curl_easy_perform() failed. "
- << "Code was \"" << res << "\". "
- << "Strerror was \"" << curl_easy_strerror(res) << "\". "
- << "Error buffer was \"" << error_buf << "\".";
- return false;
- }
- return true;
-}
-
-std::string CurlWrapper::DownloadToString(const std::string& url) {
- return DownloadToString(url, {});
-}
-
-std::string CurlWrapper::DownloadToString(const std::string& url,
- const std::vector<std::string>& headers) {
- LOG(INFO) << "Attempting to download \"" << url << "\"";
- if (!curl) {
- LOG(ERROR) << "curl was not initialized\n";
- return "";
- }
- curl_slist* curl_headers = build_slist(headers);
- curl_easy_reset(curl);
- curl_easy_setopt(curl, CURLOPT_CAINFO, "/etc/ssl/certs/ca-certificates.crt");
- curl_easy_setopt(curl, CURLOPT_HTTPHEADER, curl_headers);
- curl_easy_setopt(curl, CURLOPT_URL, url.c_str());
- std::stringstream data;
- curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, file_write_callback);
- curl_easy_setopt(curl, CURLOPT_WRITEDATA, &data);
- char error_buf[CURL_ERROR_SIZE];
- curl_easy_setopt(curl, CURLOPT_ERRORBUFFER, error_buf);
- curl_easy_setopt(curl, CURLOPT_VERBOSE, 1L);
- CURLcode res = curl_easy_perform(curl);
- if (curl_headers) {
- curl_slist_free_all(curl_headers);
- }
- if (res != CURLE_OK) {
- LOG(ERROR) << "curl_easy_perform() failed. "
- << "Code was \"" << res << "\". "
- << "Strerror was \"" << curl_easy_strerror(res) << "\". "
- << "Error buffer was \"" << error_buf << "\".";
- return "";
- }
- return data.str();
-}
-
-Json::Value CurlWrapper::DownloadToJson(const std::string& url) {
- return DownloadToJson(url, {});
-}
-
-Json::Value CurlWrapper::DownloadToJson(const std::string& url,
- const std::vector<std::string>& headers) {
- std::string contents = DownloadToString(url, headers);
- Json::CharReaderBuilder builder;
- std::unique_ptr<Json::CharReader> reader(builder.newCharReader());
- Json::Value json;
- std::string errorMessage;
- if (!reader->parse(&*contents.begin(), &*contents.end(), &json, &errorMessage)) {
- LOG(ERROR) << "Could not parse json: " << errorMessage;
- json["error"] = "Failed to parse json.";
- json["response"] = contents;
- }
- return json;
-}
-
-}
diff --git a/host/commands/fetcher/curl_wrapper.h b/host/commands/fetcher/curl_wrapper.h
deleted file mode 100644
index 6d3a2cb..0000000
--- a/host/commands/fetcher/curl_wrapper.h
+++ /dev/null
@@ -1,45 +0,0 @@
-//
-// Copyright (C) 2019 The Android Open Source Project
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#pragma once
-
-#include <string>
-
-#include <curl/curl.h>
-#include <json/json.h>
-
-namespace cuttlefish {
-
-class CurlWrapper {
- CURL* curl;
-public:
- CurlWrapper();
- ~CurlWrapper();
- CurlWrapper(const CurlWrapper&) = delete;
- CurlWrapper& operator=(const CurlWrapper*) = delete;
- CurlWrapper(CurlWrapper&&) = default;
-
- bool DownloadToFile(const std::string& url, const std::string& path);
- bool DownloadToFile(const std::string& url, const std::string& path,
- const std::vector<std::string>& headers);
- std::string DownloadToString(const std::string& url);
- std::string DownloadToString(const std::string& url,
- const std::vector<std::string>& headers);
- Json::Value DownloadToJson(const std::string& url);
- Json::Value DownloadToJson(const std::string& url,
- const std::vector<std::string>& headers);
-};
-
-}
diff --git a/host/commands/fetcher/fetch_cvd.cc b/host/commands/fetcher/fetch_cvd.cc
index b84fbba..2154233 100644
--- a/host/commands/fetcher/fetch_cvd.cc
+++ b/host/commands/fetcher/fetch_cvd.cc
@@ -13,10 +13,12 @@
// See the License for the specific language governing permissions and
// limitations under the License.
+#include <fstream>
#include <iostream>
#include <iterator>
#include <string>
+#include <curl/curl.h>
#include <sys/stat.h>
#include <unistd.h>
@@ -26,14 +28,15 @@
#include "common/libs/fs/shared_fd.h"
#include "common/libs/utils/archive.h"
+#include "common/libs/utils/environment.h"
#include "common/libs/utils/files.h"
#include "common/libs/utils/subprocess.h"
#include "host/libs/config/fetcher_config.h"
-#include "build_api.h"
-#include "credential_source.h"
-#include "install_zip.h"
+#include "host/libs/web/build_api.h"
+#include "host/libs/web/credential_source.h"
+#include "host/libs/web/install_zip.h"
namespace {
@@ -43,6 +46,7 @@
using cuttlefish::CurrentDirectory;
+DEFINE_string(api_key, "", "API key for the Android Build API");
DEFINE_string(default_build, DEFAULT_BRANCH + "/" + DEFAULT_BUILD_TARGET,
"source for the cuttlefish build to use (vendor.img + host)");
DEFINE_string(system_build, "", "source for system.img and product.img");
@@ -78,9 +82,9 @@
const std::vector<Artifact>& artifacts) {
std::string product = std::visit([](auto&& arg) { return arg.product; }, build);
auto id = std::visit([](auto&& arg) { return arg.id; }, build);
- auto match = product + "-" + name + "-" + id;
+ auto match = product + "-" + name + "-" + id + ".zip";
for (const auto& artifact : artifacts) {
- if (artifact.Name().find(match) != std::string::npos) {
+ if (artifact.Name() == match) {
return artifact.Name();
}
}
@@ -249,6 +253,31 @@
"\"branch\" - latest build of \"branch\" for \"aosp_cf_x86_phone-userdebug\"\n"
"\"build_id\" - build \"build_id\" for \"aosp_cf_x86_phone-userdebug\"\n";
+std::unique_ptr<CredentialSource> TryOpenServiceAccountFile(
+ CurlWrapper& curl, const std::string& path) {
+ LOG(VERBOSE) << "Attempting to open service account file \"" << path << "\"";
+ Json::CharReaderBuilder builder;
+ std::ifstream ifs(path);
+ Json::Value content;
+ std::string errorMessage;
+ if (!Json::parseFromStream(builder, ifs, &content, &errorMessage)) {
+ LOG(VERBOSE) << "Could not read config file \"" << path
+ << "\": " << errorMessage;
+ return {};
+ }
+ static constexpr char BUILD_SCOPE[] =
+ "https://www.googleapis.com/auth/androidbuild.internal";
+ auto result =
+ ServiceAccountOauthCredentialSource::FromJson(curl, content, BUILD_SCOPE);
+ if (!result.ok()) {
+ LOG(VERBOSE) << "Failed to load service account json file: \n"
+ << result.error();
+ return {};
+ }
+ return std::unique_ptr<CredentialSource>(
+ new ServiceAccountOauthCredentialSource(std::move(*result)));
+}
+
} // namespace
int FetchCvdMain(int argc, char** argv) {
@@ -268,13 +297,35 @@
curl_global_init(CURL_GLOBAL_DEFAULT);
{
+ auto curl = CurlWrapper::Create();
+ auto retrying_curl = CurlWrapper::WithServerErrorRetry(
+ *curl, 10, std::chrono::milliseconds(5000));
std::unique_ptr<CredentialSource> credential_source;
- if (FLAGS_credential_source == "gce") {
- credential_source = GceMetadataCredentialSource::make();
- } else if (FLAGS_credential_source != "") {
+ if (auto crds = TryOpenServiceAccountFile(*curl, FLAGS_credential_source)) {
+ credential_source = std::move(crds);
+ } else if (FLAGS_credential_source == "gce") {
+ credential_source = GceMetadataCredentialSource::make(*retrying_curl);
+ } else if (FLAGS_credential_source == "") {
+ std::string file = StringFromEnv("HOME", ".") + "/.acloud_oauth2.dat";
+ LOG(VERBOSE) << "Probing acloud credentials at " << file;
+ if (FileExists(file)) {
+ std::ifstream stream(file);
+ auto attempt_load =
+ RefreshCredentialSource::FromOauth2ClientFile(*curl, stream);
+ if (attempt_load.ok()) {
+ credential_source.reset(
+ new RefreshCredentialSource(std::move(*attempt_load)));
+ } else {
+ LOG(VERBOSE) << "Failed to load acloud credentials: "
+ << attempt_load.error();
+ }
+ } else {
+ LOG(INFO) << "\"" << file << "\" missing, running without credentials";
+ }
+ } else {
credential_source = FixedCredentialSource::make(FLAGS_credential_source);
}
- BuildApi build_api(std::move(credential_source));
+ BuildApi build_api(*retrying_curl, credential_source.get(), FLAGS_api_key);
auto default_build = ArgumentToBuild(&build_api, FLAGS_default_build,
DEFAULT_BUILD_TARGET,
@@ -293,6 +344,9 @@
if (FLAGS_otatools_build != "") {
ota_build = ArgumentToBuild(&build_api, FLAGS_otatools_build,
DEFAULT_BUILD_TARGET, retry_period);
+ } else if (FLAGS_system_build != "") {
+ ota_build = ArgumentToBuild(&build_api, FLAGS_system_build,
+ DEFAULT_BUILD_TARGET, retry_period);
}
std::vector<std::string> ota_tools_files =
download_ota_tools(&build_api, ota_build, target_dir);
diff --git a/host/commands/gnss_grpc_proxy/Android.bp b/host/commands/gnss_grpc_proxy/Android.bp
index e85a919..f490238 100644
--- a/host/commands/gnss_grpc_proxy/Android.bp
+++ b/host/commands/gnss_grpc_proxy/Android.bp
@@ -17,22 +17,10 @@
default_applicable_licenses: ["Android-Apache-2.0"],
}
-cc_binary {
- name: "gnss_grpc_proxy",
- srcs: [
- "gnss_grpc_proxy.cpp",
- ],
- cflags: [
- "-Wno-unused-parameter",
- "-D_XOPEN_SOURCE",
- ],
- generated_headers: [
- "GnssGrpcProxyStub_h",
- ],
- generated_sources: [
- "GnssGrpcProxyStub_cc",
- ],
+cc_library_static {
+ name: "libcvd_gnss_grpc_proxy",
shared_libs: [
+ "libext2_blkid",
"libbase",
"libcuttlefish_fs",
"libcuttlefish_utils",
@@ -44,6 +32,19 @@
"libcuttlefish_host_config",
"libgflags",
],
+ cflags: [
+ "-Wno-unused-parameter",
+ "-D_XOPEN_SOURCE",
+ ],
+ generated_headers: [
+ "GnssGrpcProxyStub_h",
+ ],
+ generated_sources: [
+ "GnssGrpcProxyStub_cc",
+ ],
+ export_generated_headers: [
+ "GnssGrpcProxyStub_h",
+ ],
defaults: ["cuttlefish_host"],
include_dirs: [
"external/grpc-grpc/include",
@@ -51,6 +52,32 @@
],
}
+cc_binary {
+ name: "gnss_grpc_proxy",
+ shared_libs: [
+ "libext2_blkid",
+ "libbase",
+ "libcuttlefish_fs",
+ "libcuttlefish_utils",
+ "libjsoncpp",
+ "libprotobuf-cpp-full",
+ "libgrpc++_unsecure",
+ ],
+ static_libs: [
+ "libcuttlefish_host_config",
+ "libgflags",
+ "libcvd_gnss_grpc_proxy",
+ ],
+ srcs: [
+ "gnss_grpc_proxy.cpp",
+ ],
+ cflags: [
+ "-Wno-unused-parameter",
+ "-D_XOPEN_SOURCE",
+ ],
+ defaults: ["cuttlefish_host"],
+}
+
filegroup {
name: "GnssGrpcProxyProto",
srcs: [
diff --git a/host/commands/gnss_grpc_proxy/gnss_grpc_proxy.cpp b/host/commands/gnss_grpc_proxy/gnss_grpc_proxy.cpp
index ff8e54e..c5ecc44 100644
--- a/host/commands/gnss_grpc_proxy/gnss_grpc_proxy.cpp
+++ b/host/commands/gnss_grpc_proxy/gnss_grpc_proxy.cpp
@@ -14,8 +14,10 @@
* limitations under the License.
*/
-#include <iostream>
+#include <chrono>
+#include <ctime>
#include <fstream>
+#include <iostream>
#include <memory>
#include <string>
@@ -28,11 +30,13 @@
#include <chrono>
#include <deque>
#include <mutex>
+#include <sstream>
#include <thread>
#include <vector>
-#include <gflags/gflags.h>
#include <android-base/logging.h>
+#include <android-base/strings.h>
+#include <gflags/gflags.h>
#include <common/libs/fs/shared_fd.h>
#include <common/libs/fs/shared_buf.h>
@@ -63,6 +67,9 @@
"NMEA file path for gnss grpc");
constexpr char CMD_GET_LOCATION[] = "CMD_GET_LOCATION";
+constexpr char CMD_GET_RAWMEASUREMENT[] = "CMD_GET_RAWMEASUREMENT";
+constexpr char END_OF_MSG_MARK[] = "\n\n\n\n";
+
constexpr uint32_t GNSS_SERIAL_BUFFER_SIZE = 4096;
// Logic and data behind the server's behavior.
class GnssGrpcProxyServiceImpl final : public GnssGrpcProxy::Service {
@@ -73,7 +80,6 @@
Status SendNmea(ServerContext* context, const SendNmeaRequest* request,
SendNmeaReply* reply) override {
reply->set_reply("Received nmea record.");
-
auto buffer = request->nmea();
std::lock_guard<std::mutex> lock(cached_nmea_mutex);
cached_nmea = request->nmea();
@@ -81,24 +87,54 @@
}
void sendToSerial() {
- LOG(DEBUG) << "Send NMEA to serial:" << cached_nmea;
std::lock_guard<std::mutex> lock(cached_nmea_mutex);
- ssize_t bytes_written = cuttlefish::WriteAll(gnss_in_, cached_nmea);
+ if (!isNMEA(cached_nmea)) {
+ return;
+ }
+ ssize_t bytes_written =
+ cuttlefish::WriteAll(gnss_in_, cached_nmea + END_OF_MSG_MARK);
if (bytes_written < 0) {
LOG(ERROR) << "Error writing to fd: " << gnss_in_->StrError();
}
}
+ void sendGnssRawToSerial() {
+ std::lock_guard<std::mutex> lock(cached_gnss_raw_mutex);
+ if (!isGnssRawMeasurement(cached_gnss_raw)) {
+ return;
+ }
+ if (previous_cached_gnss_raw == cached_gnss_raw) {
+ // Skip for same record
+ return;
+ } else {
+ // Update cached data
+ LOG(DEBUG) << "Skip same record";
+ previous_cached_gnss_raw = cached_gnss_raw;
+ }
+ ssize_t bytes_written =
+ cuttlefish::WriteAll(gnss_in_, cached_gnss_raw + END_OF_MSG_MARK);
+ LOG(DEBUG) << "Send Gnss Raw to serial: bytes_written: " << bytes_written;
+ if (bytes_written < 0) {
+ LOG(ERROR) << "Error writing to fd: " << gnss_in_->StrError();
+ }
+ }
+
void StartServer() {
// Create a new thread to handle writes to the gnss and to the any client
// connected to the socket.
read_thread_ = std::thread([this]() { ReadLoop(); });
}
- void StartReadFileThread() {
- // Create a new thread to handle writes to the gnss and to the any client
- // connected to the socket.
- file_read_thread_ = std::thread([this]() { ReadNmeaFromLocalFile(); });
+ void StartReadNmeaFileThread() {
+ // Create a new thread to read nmea data.
+ nmea_file_read_thread_ =
+ std::thread([this]() { ReadNmeaFromLocalFile(); });
+ }
+
+ void StartReadGnssRawMeasurementFileThread() {
+ // Create a new thread to read raw measurement data.
+ measurement_file_read_thread_ =
+ std::thread([this]() { ReadGnssRawMeasurement(); });
}
void ReadNmeaFromLocalFile() {
@@ -134,6 +170,70 @@
return;
}
}
+
+ void ReadGnssRawMeasurement() {
+ std::ifstream file(FLAGS_gnss_file_path);
+
+ if (file.is_open()) {
+ std::string line;
+ std::string cached_line = "";
+ std::string header = "";
+
+ while (!cached_line.empty() || std::getline(file, line)) {
+ if (!cached_line.empty()) {
+ line = cached_line;
+ cached_line = "";
+ }
+
+ // Get data header.
+ if (header.empty() && android::base::StartsWith(line, "# Raw")) {
+ header = line;
+ LOG(DEBUG) << "Header: " << header;
+ continue;
+ }
+
+ // Ignore not raw measurement data.
+ if (!android::base::StartsWith(line, "Raw")) {
+ continue;
+ }
+
+ {
+ std::lock_guard<std::mutex> lock(cached_gnss_raw_mutex);
+ cached_gnss_raw = header + "\n" + line;
+
+ std::string new_line = "";
+ while (std::getline(file, new_line)) {
+ // Group raw data by TimeNanos.
+ if (getTimeNanosFromLine(new_line) ==
+ getTimeNanosFromLine(line)) {
+ cached_gnss_raw += "\n" + new_line;
+ } else {
+ cached_line = new_line;
+ break;
+ }
+ }
+ }
+ std::this_thread::sleep_for(std::chrono::milliseconds(1000));
+ }
+ file.close();
+ } else {
+ LOG(ERROR) << "Can not open GNSS Raw file: " << FLAGS_gnss_file_path;
+ return;
+ }
+ }
+
+ ~GnssGrpcProxyServiceImpl() {
+ if (nmea_file_read_thread_.joinable()) {
+ nmea_file_read_thread_.join();
+ }
+ if (measurement_file_read_thread_.joinable()) {
+ measurement_file_read_thread_.join();
+ }
+ if (read_thread_.joinable()) {
+ read_thread_.join();
+ }
+ }
+
private:
[[noreturn]] void ReadLoop() {
cuttlefish::SharedFDSet read_set;
@@ -159,6 +259,12 @@
gnss_cmd_str = "";
total_read = 0;
}
+
+ if (gnss_cmd_str.find(CMD_GET_RAWMEASUREMENT) != std::string::npos) {
+ sendGnssRawToSerial();
+ gnss_cmd_str = "";
+ total_read = 0;
+ }
} else {
if (gnss_out_->GetErrno() == EAGAIN|| gnss_out_->GetErrno() == EWOULDBLOCK) {
std::this_thread::sleep_for(std::chrono::milliseconds(100));
@@ -171,12 +277,35 @@
}
}
+ std::string getTimeNanosFromLine(const std::string& line) {
+ // TimeNanos is in column #3.
+ std::vector<std::string> vals = android::base::Split(line, ",");
+ return vals.size() >= 3 ? vals[2] : "-1";
+ }
+
+ bool isGnssRawMeasurement(const std::string& inputStr) {
+ // TODO: add more logic check to by pass invalid data.
+ return !inputStr.empty() && android::base::StartsWith(inputStr, "# Raw");
+ }
+
+ bool isNMEA(const std::string& inputStr) {
+ return !inputStr.empty() &&
+ (android::base::StartsWith(inputStr, "$GPRMC") ||
+ android::base::StartsWith(inputStr, "$GPRMA"));
+ }
+
cuttlefish::SharedFD gnss_in_;
cuttlefish::SharedFD gnss_out_;
std::thread read_thread_;
- std::thread file_read_thread_;
+ std::thread nmea_file_read_thread_;
+ std::thread measurement_file_read_thread_;
+
std::string cached_nmea;
std::mutex cached_nmea_mutex;
+
+ std::string cached_gnss_raw;
+ std::string previous_cached_gnss_raw;
+ std::mutex cached_gnss_raw_mutex;
};
void RunServer() {
@@ -200,7 +329,10 @@
GnssGrpcProxyServiceImpl service(gnss_in, gnss_out);
service.StartServer();
if (!FLAGS_gnss_file_path.empty()) {
- service.StartReadFileThread();
+ // TODO: On-demand start the read file threads according to data type.
+ service.StartReadNmeaFileThread();
+ service.StartReadGnssRawMeasurementFileThread();
+
// In the local mode, we are not start a grpc server, use a infinite loop instead
while(true) {
std::this_thread::sleep_for(std::chrono::milliseconds(2000));
@@ -232,4 +364,4 @@
RunServer();
return 0;
-}
+}
\ No newline at end of file
diff --git a/host/commands/gnss_grpc_proxy/gnss_grpc_proxy.proto b/host/commands/gnss_grpc_proxy/gnss_grpc_proxy.proto
index 2a12d2d..4bfc854 100644
--- a/host/commands/gnss_grpc_proxy/gnss_grpc_proxy.proto
+++ b/host/commands/gnss_grpc_proxy/gnss_grpc_proxy.proto
@@ -2,6 +2,9 @@
package gnss_grpc_proxy;
+option java_multiple_files = true;
+option java_package = "com.android.cuttlefish.gnssproxy.proto";
+
// The greeting service definition.
service GnssGrpcProxy {
// Sends NmeaRequest
diff --git a/host/commands/stop_cvd/Android.bp b/host/commands/health/Android.bp
similarity index 87%
copy from host/commands/stop_cvd/Android.bp
copy to host/commands/health/Android.bp
index a670a25..3681332 100644
--- a/host/commands/stop_cvd/Android.bp
+++ b/host/commands/health/Android.bp
@@ -1,5 +1,5 @@
//
-// Copyright (C) 2018 The Android Open Source Project
+// Copyright (C) 2021 The Android Open Source Project
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
@@ -18,15 +18,16 @@
}
cc_binary {
- name: "stop_cvd",
+ name: "health",
srcs: [
- "main.cc",
+ "health.cpp",
],
shared_libs: [
+ "libext2_blkid",
"libbase",
"libcuttlefish_fs",
"libcuttlefish_utils",
- "libcuttlefish_allocd_utils",
+ "libfruit",
"libjsoncpp",
],
static_libs: [
diff --git a/host/commands/health/health.cpp b/host/commands/health/health.cpp
new file mode 100644
index 0000000..37cce86
--- /dev/null
+++ b/host/commands/health/health.cpp
@@ -0,0 +1,130 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <stdlib.h>
+#include <iostream>
+//
+#include <android-base/logging.h>
+#include <gflags/gflags.h>
+//
+#include "host/libs/config/cuttlefish_config.h"
+#include "host/libs/vm_manager/vm_manager.h"
+
+std::string GetControlSocketPath(const cuttlefish::CuttlefishConfig& config) {
+ return config.ForDefaultInstance().PerInstanceInternalPath(
+ "crosvm_control.sock");
+}
+
+std::string USAGE_MESSAGE =
+ "<key> [value]\n"
+ "Excluding the value will enumerate the possible values to set\n"
+ "\n"
+ "\"status [value]\" - battery status: "
+ "unknown/charging/discharging/notcharging/full\n"
+ "\"health [value]\" - battery health\n"
+ "\"present [value]\" - battery present: 1 or 0\n"
+ "\"capacity [value]\" - battery capacity: 0 to 100\n"
+ "\"aconline [value]\" - battery ac online: 1 or 0\n";
+
+int status() {
+ std::cout
+ << "health status [value]\n"
+ "\"value\" - unknown, charging, discharging, notcharging, full\n";
+ return 0;
+}
+
+int health() {
+ std::cout << "health health [value]\n"
+ "\"value\" - unknown, good, overheat, dead, overvoltage, "
+ "unexpectedfailure,\n"
+ " cold, watchdogtimerexpire, safetytimerexpire, "
+ "overcurrent\n";
+ return 0;
+}
+
+int present() {
+ std::cout << "health present [value]\n"
+ "\"value\" - 1, 0\n";
+ return 0;
+}
+
+int capacity() {
+ std::cout << "health capacity [value]\n"
+ "\"value\" - 0 to 100\n";
+ return 0;
+}
+
+int aconline() {
+ std::cout << "health aconline [value]\n"
+ "\"value\" - 1, 0\n";
+ return 0;
+}
+
+int usage() {
+ std::cout << "health " << USAGE_MESSAGE;
+ return 1;
+}
+
+int main(int argc, char** argv) {
+ ::android::base::InitLogging(argv, android::base::StderrLogger);
+ gflags::SetUsageMessage(USAGE_MESSAGE);
+
+ auto config = cuttlefish::CuttlefishConfig::Get();
+ if (!config) {
+ LOG(ERROR) << "Failed to obtain config object";
+ return 1;
+ }
+
+ if (argc != 2 && argc != 3) {
+ return usage();
+ }
+
+ std::string key = argv[1];
+ std::string value = "";
+ if (argc == 3) {
+ value = argv[2];
+ }
+
+ if (argc == 2 || value == "--help" || value == "-h" || value == "help") {
+ if (key == "status") {
+ return status();
+ } else if (key == "health") {
+ return health();
+ } else if (key == "present") {
+ return present();
+ } else if (key == "capacity") {
+ return capacity();
+ } else if (key == "aconline") {
+ return aconline();
+ } else {
+ return usage();
+ }
+ }
+
+ cuttlefish::Command command(config->crosvm_binary());
+ command.AddParameter("battery");
+ command.AddParameter("goldfish");
+ command.AddParameter(key);
+ command.AddParameter(value);
+ command.AddParameter(GetControlSocketPath(*config));
+
+ std::string output, error;
+ auto ret = RunWithManagedStdio(std::move(command), NULL, &output, &error);
+ if (ret != 0) {
+ LOG(ERROR) << "goldfish battery returned: " << ret << "\n" << output << "\n" << error;
+ }
+ return ret;
+}
diff --git a/host/commands/cvd_host_bugreport/Android.bp b/host/commands/host_bugreport/Android.bp
similarity index 89%
rename from host/commands/cvd_host_bugreport/Android.bp
rename to host/commands/host_bugreport/Android.bp
index 4f6aeda..76f184b 100644
--- a/host/commands/cvd_host_bugreport/Android.bp
+++ b/host/commands/host_bugreport/Android.bp
@@ -18,14 +18,17 @@
}
cc_binary {
- name: "cvd_host_bugreport",
+ name: "cvd_internal_host_bugreport",
+ symlinks: ["cvd_host_bugreport"],
srcs: [
"main.cc",
],
shared_libs: [
+ "libext2_blkid",
"libbase",
"libcuttlefish_fs",
"libcuttlefish_utils",
+ "libfruit",
"libjsoncpp",
"libziparchive",
],
diff --git a/host/commands/cvd_host_bugreport/main.cc b/host/commands/host_bugreport/main.cc
similarity index 100%
rename from host/commands/cvd_host_bugreport/main.cc
rename to host/commands/host_bugreport/main.cc
diff --git a/host/commands/kernel_log_monitor/Android.bp b/host/commands/kernel_log_monitor/Android.bp
index 02e2be8..7421115 100644
--- a/host/commands/kernel_log_monitor/Android.bp
+++ b/host/commands/kernel_log_monitor/Android.bp
@@ -24,6 +24,7 @@
"kernel_log_server.cc",
],
shared_libs: [
+ "libext2_blkid",
"libcuttlefish_fs",
"libcuttlefish_utils",
"libcuttlefish_kernel_log_monitor_utils",
diff --git a/host/commands/kernel_log_monitor/kernel_log_server.cc b/host/commands/kernel_log_monitor/kernel_log_server.cc
index 03fc90d..fe3a588 100644
--- a/host/commands/kernel_log_monitor/kernel_log_server.cc
+++ b/host/commands/kernel_log_monitor/kernel_log_server.cc
@@ -16,7 +16,8 @@
#include "host/commands/kernel_log_monitor/kernel_log_server.h"
-#include <map>
+#include <string>
+#include <tuple>
#include <utility>
#include <android-base/logging.h>
@@ -25,29 +26,45 @@
#include "common/libs/fs/shared_select.h"
#include "host/libs/config/cuttlefish_config.h"
-using cuttlefish::SharedFD;
-
namespace {
-static const std::map<std::string, std::string> kInformationalPatterns = {
+
+using cuttlefish::SharedFD;
+using monitor::Event;
+
+constexpr struct {
+ std::string_view match; // Substring to match in the kernel logs
+ std::string_view prefix; // Prefix value to output, describing the entry
+} kInformationalPatterns[] = {
{"U-Boot ", "GUEST_UBOOT_VERSION: "},
{"] Linux version ", "GUEST_KERNEL_VERSION: "},
{"GUEST_BUILD_FINGERPRINT: ", "GUEST_BUILD_FINGERPRINT: "},
};
-static const std::map<std::string, monitor::Event> kStageToEventMap = {
- {cuttlefish::kBootStartedMessage, monitor::Event::BootStarted},
- {cuttlefish::kBootCompletedMessage, monitor::Event::BootCompleted},
- {cuttlefish::kBootFailedMessage, monitor::Event::BootFailed},
- {cuttlefish::kMobileNetworkConnectedMessage,
- monitor::Event::MobileNetworkConnected},
- {cuttlefish::kWifiConnectedMessage, monitor::Event::WifiNetworkConnected},
- {cuttlefish::kEthernetConnectedMessage,
- monitor::Event::EthernetNetworkConnected},
+enum EventFormat {
+ kBare, // Just an event, no extra data
+ kKeyValuePair, // <stage> <key>=<value>
+};
+
+constexpr struct {
+ std::string_view stage; // substring in the log identifying the stage
+ Event event; // emitted when the stage is encountered
+ EventFormat format; // how the log message is formatted
+} kStageTable[] = {
+ {cuttlefish::kBootStartedMessage, Event::BootStarted, kBare},
+ {cuttlefish::kBootCompletedMessage, Event::BootCompleted, kBare},
+ {cuttlefish::kBootFailedMessage, Event::BootFailed, kKeyValuePair},
+ {cuttlefish::kMobileNetworkConnectedMessage, Event::MobileNetworkConnected,
+ kBare},
+ {cuttlefish::kWifiConnectedMessage, Event::WifiNetworkConnected, kBare},
+ {cuttlefish::kEthernetConnectedMessage, Event::EthernetNetworkConnected,
+ kBare},
// TODO(b/131864854): Replace this with a string less likely to change
- {"init: starting service 'adbd'...", monitor::Event::AdbdStarted},
- {cuttlefish::kScreenChangedMessage, monitor::Event::ScreenChanged},
+ {"init: starting service 'adbd'...", Event::AdbdStarted, kBare},
+ {cuttlefish::kScreenChangedMessage, Event::ScreenChanged, kKeyValuePair},
+ {cuttlefish::kBootloaderLoadedMessage, Event::BootloaderLoaded, kBare},
+ {cuttlefish::kKernelLoadedMessage, Event::KernelLoaded, kBare},
{cuttlefish::kDisplayPowerModeChangedMessage,
- monitor::Event::DisplayPowerModeChanged},
+ monitor::Event::DisplayPowerModeChanged, kKeyValuePair},
};
void ProcessSubscriptions(
@@ -112,17 +129,13 @@
// Detect VIRTUAL_DEVICE_BOOT_*
for (ssize_t i=0; i<ret; i++) {
if ('\n' == buf[i]) {
- for (auto& info_kv : kInformationalPatterns) {
- auto& match = info_kv.first;
- auto& prefix = info_kv.second;
+ for (auto& [match, prefix] : kInformationalPatterns) {
auto pos = line_.find(match);
if (std::string::npos != pos) {
LOG(INFO) << prefix << line_.substr(pos + match.size());
}
}
- for (auto& stage_kv : kStageToEventMap) {
- auto& stage = stage_kv.first;
- auto event = stage_kv.second;
+ for (const auto& [stage, event, format] : kStageTable) {
auto pos = line_.find(stage);
if (std::string::npos != pos) {
// Log the stage
@@ -131,23 +144,26 @@
Json::Value message;
message["event"] = event;
Json::Value metadata;
- // Expect space-separated key=value pairs in the log message.
- const auto& fields = android::base::Split(
- line_.substr(pos + stage.size()), " ");
- for (std::string field : fields) {
- field = android::base::Trim(field);
- if (field.empty()) {
- // Expected; android::base::Split() always returns at least
- // one (possibly empty) string.
- LOG(DEBUG) << "Empty field for line: " << line_;
- continue;
+
+ if (format == kKeyValuePair) {
+ // Expect space-separated key=value pairs in the log message.
+ const auto& fields =
+ android::base::Split(line_.substr(pos + stage.size()), " ");
+ for (std::string field : fields) {
+ field = android::base::Trim(field);
+ if (field.empty()) {
+ // Expected; android::base::Split() always returns at least
+ // one (possibly empty) string.
+ LOG(DEBUG) << "Empty field for line: " << line_;
+ continue;
+ }
+ const auto& keyvalue = android::base::Split(field, "=");
+ if (keyvalue.size() != 2) {
+ LOG(WARNING) << "Field is not in key=value format: " << field;
+ continue;
+ }
+ metadata[keyvalue[0]] = keyvalue[1];
}
- const auto& keyvalue = android::base::Split(field, "=");
- if (keyvalue.size() != 2) {
- LOG(WARNING) << "Field is not in key=value format: " << field;
- continue;
- }
- metadata[keyvalue[0]] = keyvalue[1];
}
message["metadata"] = metadata;
ProcessSubscriptions(message, &subscribers_);
@@ -157,7 +173,7 @@
if (deprecated_boot_completed_) {
// Write to host kernel log
FILE* log = popen("/usr/bin/sudo /usr/bin/tee /dev/kmsg", "w");
- fprintf(log, "%s\n", stage.c_str());
+ fprintf(log, "%s\n", std::string(stage).c_str());
fclose(log);
}
}
diff --git a/host/commands/kernel_log_monitor/kernel_log_server.h b/host/commands/kernel_log_monitor/kernel_log_server.h
index dab675d..754ff24 100644
--- a/host/commands/kernel_log_monitor/kernel_log_server.h
+++ b/host/commands/kernel_log_monitor/kernel_log_server.h
@@ -37,7 +37,11 @@
AdbdStarted = 5,
ScreenChanged = 6,
EthernetNetworkConnected = 7,
- DisplayPowerModeChanged = 9,
+ KernelLoaded = 8, // BootStarted actually comes quite late in the boot.
+ BootloaderLoaded = 9, /* BootloaderLoaded is the earliest possible indicator
+ * that we're booting a device.
+ */
+ DisplayPowerModeChanged = 10,
};
enum class SubscriptionAction {
diff --git a/host/commands/kernel_log_monitor/main.cc b/host/commands/kernel_log_monitor/main.cc
index 3f708b7..c7269b0 100644
--- a/host/commands/kernel_log_monitor/main.cc
+++ b/host/commands/kernel_log_monitor/main.cc
@@ -94,7 +94,7 @@
return 2;
}
- monitor::KernelLogServer klog{pipe, instance.PerInstancePath("kernel.log"),
+ monitor::KernelLogServer klog{pipe, instance.PerInstanceLogPath("kernel.log"),
config->deprecated_boot_completed()};
for (auto subscriber_fd: subscriber_fds) {
diff --git a/host/commands/log_tee/Android.bp b/host/commands/log_tee/Android.bp
index 49b3e69..ed1f652 100644
--- a/host/commands/log_tee/Android.bp
+++ b/host/commands/log_tee/Android.bp
@@ -23,9 +23,11 @@
"log_tee.cpp",
],
shared_libs: [
+ "libext2_blkid",
"libcuttlefish_fs",
"libcuttlefish_utils",
"libbase",
+ "libfruit",
"libjsoncpp",
"libnl",
],
diff --git a/host/commands/log_tee/log_tee.cpp b/host/commands/log_tee/log_tee.cpp
index 7021a99..b63c2ac 100644
--- a/host/commands/log_tee/log_tee.cpp
+++ b/host/commands/log_tee/log_tee.cpp
@@ -64,7 +64,7 @@
// There is no guarantee of success all the time since log line boundaries
// could be out sync with the reads, but that's ok.
if (android::base::StartsWith(trimmed, "[INFO")) {
- LOG(INFO) << trimmed;
+ LOG(DEBUG) << trimmed;
} else if (android::base::StartsWith(trimmed, "[ERROR")) {
LOG(ERROR) << trimmed;
} else if (android::base::StartsWith(trimmed, "[WARNING")) {
diff --git a/host/commands/logcat_receiver/Android.bp b/host/commands/logcat_receiver/Android.bp
index 305ab7f..63f5b1b 100644
--- a/host/commands/logcat_receiver/Android.bp
+++ b/host/commands/logcat_receiver/Android.bp
@@ -23,6 +23,7 @@
"main.cpp",
],
shared_libs: [
+ "libext2_blkid",
"libbase",
"libcuttlefish_fs",
"libjsoncpp",
diff --git a/host/commands/metrics/Android.bp b/host/commands/metrics/Android.bp
index 67eb3b0..c454b53 100644
--- a/host/commands/metrics/Android.bp
+++ b/host/commands/metrics/Android.bp
@@ -23,6 +23,7 @@
"metrics.cc",
],
shared_libs: [
+ "libext2_blkid",
"libcuttlefish_fs",
"libcuttlefish_utils",
"libbase",
diff --git a/host/commands/metrics/metrics.cc b/host/commands/metrics/metrics.cc
index 378155c..5fcf58e 100644
--- a/host/commands/metrics/metrics.cc
+++ b/host/commands/metrics/metrics.cc
@@ -32,7 +32,7 @@
CHECK(config) << "Could not open cuttlefish config";
auto instance = config->ForDefaultInstance();
- auto metrics_log_path = instance.PerInstancePath("metrics.log");
+ auto metrics_log_path = instance.PerInstanceLogPath("metrics.log");
if (config->run_as_daemon()) {
android::base::SetLogger(
diff --git a/host/commands/mk_cdisk/mk_cdisk.cc b/host/commands/mk_cdisk/mk_cdisk.cc
deleted file mode 100644
index 028547c..0000000
--- a/host/commands/mk_cdisk/mk_cdisk.cc
+++ /dev/null
@@ -1,146 +0,0 @@
-//
-// Copyright (C) 2021 The Android Open Source Project
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include <fstream>
-#include <iostream>
-
-#include <android-base/file.h>
-#include <android-base/logging.h>
-#include <android-base/result.h>
-#include <json/json.h>
-
-#include "common/libs/utils/files.h"
-#include "host/libs/image_aggregator/image_aggregator.h"
-
-using android::base::ErrnoError;
-using android::base::Error;
-using android::base::Result;
-
-using cuttlefish::CreateCompositeDisk;
-using cuttlefish::FileExists;
-using cuttlefish::ImagePartition;
-using cuttlefish::kLinuxFilesystem;
-
-// Returns `append` is appended to the end of filename preserving the extension.
-std::string AppendFileName(const std::string& filename,
- const std::string& append) {
- size_t pos = filename.find_last_of('.');
- if (pos == std::string::npos) {
- return filename + append;
- } else {
- return filename.substr(0, pos) + append + filename.substr(pos);
- }
-}
-
-// config JSON schema:
-// {
-// "partitions": [
-// {
-// "label": string,
-// "path": string,
-// "writable": bool, // optional. defaults to false.
-// }
-// ]
-// }
-
-Result<std::vector<ImagePartition>> LoadConfig(std::istream& in) {
- std::vector<ImagePartition> partitions;
-
- Json::CharReaderBuilder builder;
- Json::Value root;
- Json::String errs;
- if (!parseFromStream(builder, in, &root, &errs)) {
- return Error() << "bad config: " << errs;
- }
- for (const Json::Value& part : root["partitions"]) {
- const std::string label = part["label"].asString();
- const std::string path = part["path"].asString();
- const bool writable =
- part["writable"].asBool(); // default: false (if null)
-
- if (!FileExists(path)) {
- return Error() << "bad config: Can't find \'" << path << '\'';
- }
- partitions.push_back(
- ImagePartition{label, path, kLinuxFilesystem, .read_only = !writable});
- }
-
- if (partitions.empty()) {
- return Error() << "bad config: no partitions";
- }
- return partitions;
-}
-
-Result<std::vector<ImagePartition>> LoadConfig(const std::string& config_file) {
- if (config_file == "-") {
- return LoadConfig(std::cin);
- } else {
- std::ifstream in(config_file);
- if (!in) {
- return ErrnoError() << "Can't open file \'" << config_file << '\'';
- }
- return LoadConfig(in);
- }
-}
-
-struct CompositeDiskArgs {
- std::string config_file;
- std::string output_file;
-};
-
-Result<CompositeDiskArgs> ParseCompositeDiskArgs(int argc, char** argv) {
- if (argc != 3) {
- std::cerr << fmt::format(
- "Usage: {0} <config_file> <output_file>\n"
- " or {0} - <output_file> (read config from STDIN)\n",
- argv[0]);
- return Error() << "missing arguments.";
- }
- CompositeDiskArgs args{
- .config_file = argv[1],
- .output_file = argv[2],
- };
- return args;
-}
-
-Result<void> MakeCompositeDiskMain(int argc, char** argv) {
- setenv("ANDROID_LOG_TAGS", "*:v", /* overwrite */ 0);
- ::android::base::InitLogging(argv, android::base::StderrLogger);
-
- auto args = ParseCompositeDiskArgs(argc, argv);
- if (!args.ok()) {
- return args.error();
- }
- auto partitions = LoadConfig(args->config_file);
- if (!partitions.ok()) {
- return partitions.error();
- }
-
- // We need two implicit output paths: GPT header/footer
- // e.g. out.img will have out-header.img and out-footer.img
- std::string gpt_header = AppendFileName(args->output_file, "-header");
- std::string gpt_footer = AppendFileName(args->output_file, "-footer");
- CreateCompositeDisk(*partitions, gpt_header, gpt_footer, args->output_file);
- return {};
-}
-
-int main(int argc, char** argv) {
- auto result = MakeCompositeDiskMain(argc, argv);
- if (!result.ok()) {
- LOG(ERROR) << result.error();
- return EXIT_FAILURE;
- }
- return 0;
-}
diff --git a/host/commands/mkenvimage_slim/Android.bp b/host/commands/mkenvimage_slim/Android.bp
new file mode 100644
index 0000000..2733261
--- /dev/null
+++ b/host/commands/mkenvimage_slim/Android.bp
@@ -0,0 +1,48 @@
+//
+// Copyright (C) 2022 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package {
+ default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+cc_binary {
+ name: "mkenvimage_slim",
+ defaults: ["cuttlefish_host", "cuttlefish_libicuuc"],
+ srcs: [
+ "mkenvimage_slim.cc",
+ ],
+ target: {
+ host: {
+ static_libs: [
+ "libbase",
+ "libcuttlefish_fs",
+ "libcuttlefish_utils",
+ "libgflags",
+ "liblog",
+ "libz",
+ ],
+ },
+ android: {
+ shared_libs: [
+ "libbase",
+ "libcuttlefish_fs",
+ "libcuttlefish_utils",
+ "libgflags",
+ "liblog",
+ "libz",
+ ],
+ },
+ },
+}
diff --git a/host/commands/mkenvimage_slim/mkenvimage_slim.cc b/host/commands/mkenvimage_slim/mkenvimage_slim.cc
new file mode 100644
index 0000000..ebd1b9d
--- /dev/null
+++ b/host/commands/mkenvimage_slim/mkenvimage_slim.cc
@@ -0,0 +1,92 @@
+//
+// Copyright (C) 2019 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+// Code here has been inspired by
+// https://github.com/u-boot/u-boot/blob/master/tools/mkenvimage.c The bare
+// minimum amount of functionality for our application is replicated.
+
+#include <iostream>
+
+#include <zlib.h>
+
+#include <android-base/logging.h>
+#include <android-base/strings.h>
+#include <gflags/gflags.h>
+
+#include "common/libs/fs/shared_buf.h"
+#include "common/libs/fs/shared_fd.h"
+#include "common/libs/utils/files.h"
+#include "common/libs/utils/result.h"
+
+#define PAD_VALUE (0xff)
+#define CRC_SIZE (sizeof(uint32_t))
+
+// One NULL needed at the end of the env.
+#define NULL_PAD_LENGTH (1)
+
+DEFINE_uint32(env_size, 4096, "file size of resulting env");
+DEFINE_string(output_path, "", "output file path");
+DEFINE_string(input_path, "", "input file path");
+namespace cuttlefish {
+namespace {
+std::string USAGE_MESSAGE =
+ "<flags>\n"
+ "\n"
+ "env_size - length in bytes of the resulting env image. Defaults to 4kb.\n"
+ "input_path - path to input key value mapping as a text file\n"
+ "output_path - path to write resulting environment image including CRC "
+ "to\n";
+} // namespace
+
+Result<int> MkenvimageSlimMain(int argc, char** argv) {
+ ::android::base::InitLogging(argv, android::base::StderrLogger);
+ gflags::SetUsageMessage(USAGE_MESSAGE);
+ gflags::ParseCommandLineFlags(&argc, &argv, true);
+ CF_EXPECT(FLAGS_output_path != "", "Output env path isn't defined.");
+ CF_EXPECT(FLAGS_env_size != 0, "env size can't be 0.");
+ CF_EXPECT(!(FLAGS_env_size % 512), "env size must be multiple of 512.");
+
+ std::string env_readout = ReadFile(FLAGS_input_path);
+ CF_EXPECT(env_readout.length(), "Input env is empty");
+ CF_EXPECT(
+ env_readout.length() <= (FLAGS_env_size - CRC_SIZE - NULL_PAD_LENGTH),
+ "Input env must fit within env_size specified.");
+
+ std::vector<uint8_t> env_buffer(FLAGS_env_size, PAD_VALUE);
+ uint8_t* env_ptr = env_buffer.data() + CRC_SIZE;
+ memcpy(env_ptr, env_readout.c_str(), FileSize(FLAGS_input_path));
+ env_ptr[env_readout.length()] = 0; // final byte after the env must be NULL
+ uint32_t crc = crc32(0, env_ptr, FLAGS_env_size - CRC_SIZE);
+ memcpy(env_buffer.data(), &crc, sizeof(uint32_t));
+
+ auto output_fd =
+ SharedFD::Creat(FLAGS_output_path, S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP);
+ if (!output_fd->IsOpen()) {
+ return CF_ERR("Couldn't open the output file " + FLAGS_output_path);
+ } else if (FLAGS_env_size !=
+ WriteAll(output_fd, (char*)env_buffer.data(), FLAGS_env_size)) {
+ RemoveFile(FLAGS_output_path);
+ return CF_ERR("Couldn't complete write to " + FLAGS_output_path);
+ }
+
+ return 0;
+}
+} // namespace cuttlefish
+
+int main(int argc, char** argv) {
+ auto res = cuttlefish::MkenvimageSlimMain(argc, argv);
+ CHECK(res.ok()) << "mkenvimage_slim failed: \n" << res.error();
+ return *res;
+}
diff --git a/host/commands/modem_simulator/Android.bp b/host/commands/modem_simulator/Android.bp
index c0d0371..c9a6bfb 100644
--- a/host/commands/modem_simulator/Android.bp
+++ b/host/commands/modem_simulator/Android.bp
@@ -38,6 +38,7 @@
"nvram_config.cpp"
],
shared_libs: [
+ "libext2_blkid",
"libcuttlefish_fs",
"libcuttlefish_utils",
"libbase",
diff --git a/host/commands/modem_simulator/call_service.cpp b/host/commands/modem_simulator/call_service.cpp
index 678da4f..674c153 100644
--- a/host/commands/modem_simulator/call_service.cpp
+++ b/host/commands/modem_simulator/call_service.cpp
@@ -194,7 +194,7 @@
client.SendCommandResponse(kCmeErrorNoNetworkService);
return;
}
- auto local_host_port = GetHostPort();
+ auto local_host_port = GetHostId();
if (local_host_port == remote_port) {
client.SendCommandResponse(kCmeErrorOperationNotAllowed);
return;
@@ -219,11 +219,13 @@
int index = last_active_call_index_++;
auto call_token = std::make_pair(index, call_status.number);
- call_status.timeout_serial = thread_looper_->PostWithDelay(
- std::chrono::minutes(1),
- makeSafeCallback<CallService>(this, [call_token](CallService* me) {
- me->TimerWaitingRemoteCallResponse(call_token);
- }));
+ call_status.timeout_serial = thread_looper_->Post(
+ makeSafeCallback<CallService>(this,
+ [call_token](CallService* me) {
+ me->TimerWaitingRemoteCallResponse(
+ call_token);
+ }),
+ std::chrono::minutes(1));
active_calls_[index] = call_status;
} else {
@@ -237,8 +239,9 @@
in_emergency_mode_ = true;
SendUnsolicitedCommand("+WSOS: 1");
}
- thread_looper_->PostWithDelay(std::chrono::seconds(1),
- makeSafeCallback(this, &CallService::SimulatePendingCallsAnswered));
+ thread_looper_->Post(
+ makeSafeCallback(this, &CallService::SimulatePendingCallsAnswered),
+ std::chrono::seconds(1));
}
client.SendCommandResponse("OK");
@@ -249,11 +252,9 @@
CallStatus::CallState state) {
if (call.is_remote_call && call.remote_client != std::nullopt) {
std::stringstream ss;
- ss << "AT+REMOTECALL=" << state << ","
- << call.is_voice_mode << ","
- << call.is_multi_party << ",\""
- << GetHostPort() << "\","
- << call.is_international;
+ ss << "AT+REMOTECALL=" << state << "," << call.is_voice_mode << ","
+ << call.is_multi_party << ",\"" << GetHostId() << "\","
+ << call.is_international;
SendCommandToRemote(*(call.remote_client), ss.str());
if (state == CallStatus::CALL_STATE_HANGUP) {
@@ -702,7 +703,7 @@
call_status.is_voice_mode = mode;
call_status.is_multi_party = mpty;
call_status.is_mobile_terminated = true;
- call_status.is_international = num_type;
+ call_status.is_international = (num_type == 145);
call_status.remote_client = client.client_fd;
call_status.call_state = CallStatus::CALL_STATE_INCOMING;
diff --git a/host/commands/modem_simulator/cf_device_config.cpp b/host/commands/modem_simulator/cf_device_config.cpp
index 559e1f8..1201db5 100644
--- a/host/commands/modem_simulator/cf_device_config.cpp
+++ b/host/commands/modem_simulator/cf_device_config.cpp
@@ -22,14 +22,13 @@
namespace cuttlefish {
namespace modem {
-int DeviceConfig::host_port() {
+int DeviceConfig::host_id() {
if (!cuttlefish::CuttlefishConfig::Get()) {
- return 6500;
+ return 1000;
}
auto config = cuttlefish::CuttlefishConfig::Get();
auto instance = config->ForDefaultInstance();
- auto host_port = instance.host_port();
- return host_port;
+ return instance.modem_simulator_host_id();
}
std::string DeviceConfig::PerInstancePath(const char* file_name) {
@@ -72,5 +71,13 @@
return ril_config.dns();
}
+std::ifstream DeviceConfig::open_ifstream_crossplat(const char* filename) {
+ return std::ifstream(filename);
+}
+
+std::ofstream DeviceConfig::open_ofstream_crossplat(const char* filename, std::ios_base::openmode mode) {
+ return std::ofstream(filename, mode);
+}
+
} // namespace modem
} // namespace cuttlefish
diff --git a/host/commands/modem_simulator/channel_monitor.cpp b/host/commands/modem_simulator/channel_monitor.cpp
index e598f2d..941a8b4 100644
--- a/host/commands/modem_simulator/channel_monitor.cpp
+++ b/host/commands/modem_simulator/channel_monitor.cpp
@@ -45,7 +45,7 @@
if (response.back() != '\r') {
response += '\r';
}
- LOG(VERBOSE) << " AT< " << response;
+ LOG(DEBUG) << " AT< " << response;
std::lock_guard<std::mutex> autolock(const_cast<Client*>(this)->write_mutex);
client_fd->Write(response.data(), response.size());
@@ -153,7 +153,7 @@
if (r_pos != std::string::npos) {
auto command = commands.substr(pos, r_pos - pos);
if (command.size() > 0) { // "\r\r" ?
- LOG(VERBOSE) << "AT> " << command;
+ LOG(DEBUG) << "AT> " << command;
modem_->DispatchCommand(client, command);
}
pos = r_pos + 1; // Skip '\r'
diff --git a/host/commands/modem_simulator/data_service.cpp b/host/commands/modem_simulator/data_service.cpp
index b48a777..1e11e69 100644
--- a/host/commands/modem_simulator/data_service.cpp
+++ b/host/commands/modem_simulator/data_service.cpp
@@ -335,10 +335,10 @@
// call again after 1 sec delay
count--;
- thread_looper_->PostWithDelay(
- std::chrono::seconds(1),
+ thread_looper_->Post(
makeSafeCallback(this, &DataService::updatePhysicalChannelconfigs,
- modem_tech, freq, cellBandwidthDownlink, count));
+ modem_tech, freq, cellBandwidthDownlink, count),
+ std::chrono::seconds(1));
}
} // namespace cuttlefish
diff --git a/host/commands/modem_simulator/device_config.h b/host/commands/modem_simulator/device_config.h
index 05bca77..c357356 100644
--- a/host/commands/modem_simulator/device_config.h
+++ b/host/commands/modem_simulator/device_config.h
@@ -17,7 +17,8 @@
#pragma once
#include <string>
-
+#include <fstream>
+
// this file provide a few device (cvd or emulator) specific hooks for
// modem-simulator
@@ -26,12 +27,14 @@
class DeviceConfig {
public:
- static int host_port();
+ static int host_id();
static std::string PerInstancePath(const char* file_name);
static std::string DefaultHostArtifactsPath(const std::string& file);
static std::string ril_address_and_prefix();
static std::string ril_gateway();
static std::string ril_dns();
+ static std::ifstream open_ifstream_crossplat(const char* filename);
+ static std::ofstream open_ofstream_crossplat(const char* filename, std::ios_base::openmode mode = std::ios_base::out);
};
} // namespace modem
diff --git a/host/commands/modem_simulator/main.cpp b/host/commands/modem_simulator/main.cpp
index 794ad77..41ab6ce 100644
--- a/host/commands/modem_simulator/main.cpp
+++ b/host/commands/modem_simulator/main.cpp
@@ -63,7 +63,7 @@
auto config = cuttlefish::CuttlefishConfig::Get();
auto instance = config->ForDefaultInstance();
- auto modem_log_path = instance.PerInstancePath("modem_simulator.log");
+ auto modem_log_path = instance.PerInstanceLogPath("modem_simulator.log");
{
auto log_path = instance.launcher_log_path();
@@ -114,7 +114,7 @@
// remote call, remote sms from other cuttlefish instance
std::string monitor_socket_name = "modem_simulator";
std::stringstream ss;
- ss << instance.host_port();
+ ss << instance.modem_simulator_host_id();
monitor_socket_name.append(ss.str());
auto monitor_socket = cuttlefish::SharedFD::SocketLocalServer(
diff --git a/host/commands/modem_simulator/misc_service.cpp b/host/commands/modem_simulator/misc_service.cpp
index 1a2b767..005d9f9 100644
--- a/host/commands/modem_simulator/misc_service.cpp
+++ b/host/commands/modem_simulator/misc_service.cpp
@@ -15,10 +15,11 @@
#include "host/commands/modem_simulator/misc_service.h"
-#include <ctime>
#include <fstream>
#include <iomanip>
+#include "host/commands/modem_simulator/device_config.h"
+
namespace cuttlefish {
MiscService::MiscService(int32_t service_id, ChannelMonitor* channel_monitor,
@@ -31,7 +32,7 @@
void MiscService::ParseTimeZone() {
#if defined(__linux__)
constexpr char TIMEZONE_FILENAME[] = "/etc/timezone";
- std::ifstream ifs(TIMEZONE_FILENAME);
+ std::ifstream ifs = modem::DeviceConfig::open_ifstream_crossplat(TIMEZONE_FILENAME);
if (ifs.is_open()) {
std::string line;
if (std::getline(ifs, line)) {
@@ -95,6 +96,10 @@
[this](const Client& client, std::string& cmd) {
this->HandleGetIMEI(client, cmd);
}),
+ CommandHandler("+REMOTETIMEUPDATE",
+ [this](const Client& client, std::string& cmd) {
+ this->HandleTimeUpdate(client, cmd);
+ }),
};
return (command_handlers);
}
@@ -135,31 +140,43 @@
client.SendCommandResponse(responses);
}
+void MiscService::HandleTimeUpdate(const Client& client, std::string& command) {
+ (void)client;
+ (void)command;
+ TimeUpdate();
+}
+
+long MiscService::TimeZoneOffset(time_t* utctime)
+{
+ struct tm local = *std::localtime(utctime);
+ time_t local_time = std::mktime(&local);
+ struct tm gmt = *std::gmtime(utctime);
+ // mktime() converts struct tm according to local timezone.
+ time_t gmt_time = std::mktime(&gmt);
+ return (long)difftime(local_time, gmt_time);
+}
+
void MiscService::TimeUpdate() {
auto now = std::time(0);
auto local_time = *std::localtime(&now);
auto gm_time = *std::gmtime(&now);
- auto t_local_time = std::mktime(&local_time);
- auto t_gm_time = std::mktime(&gm_time);
-
// Timezone offset is in number of quarter-hours
- auto tzdiff = (int)std::difftime(t_local_time, t_gm_time) / (15 * 60);
+ auto tzdiff = TimeZoneOffset(&now) / (15 * 60);
std::stringstream ss;
- ss << "%CTZV: \"" << std::setfill('0') << std::setw(2)
- << local_time.tm_year % 100 << "/" << std::setfill('0') << std::setw(2)
- << local_time.tm_mon + 1 << "/" << std::setfill('0') << std::setw(2)
- << local_time.tm_mday << "," << std::setfill('0') << std::setw(2)
- << local_time.tm_hour << ":" << std::setfill('0') << std::setw(2)
- << local_time.tm_min << ":" << std::setfill('0') << std::setw(2)
- << local_time.tm_sec << (tzdiff >= 0 ? '+' : '-')
+ ss << "%CTZV: " << std::setfill('0') << std::setw(2)
+ << gm_time.tm_year % 100 << "/" << std::setfill('0') << std::setw(2)
+ << gm_time.tm_mon + 1 << "/" << std::setfill('0') << std::setw(2)
+ << gm_time.tm_mday << ":" << std::setfill('0') << std::setw(2)
+ << gm_time.tm_hour << ":" << std::setfill('0') << std::setw(2)
+ << gm_time.tm_min << ":" << std::setfill('0') << std::setw(2)
+ << gm_time.tm_sec << (tzdiff >= 0 ? '+' : '-')
<< (tzdiff >= 0 ? tzdiff : -tzdiff) << ":" << local_time.tm_isdst;
if (!timezone_.empty()) {
ss << ":" << timezone_;
}
- ss << "\"";
SendUnsolicitedCommand(ss.str());
}
diff --git a/host/commands/modem_simulator/misc_service.h b/host/commands/modem_simulator/misc_service.h
index e795a06..bb4c96c 100644
--- a/host/commands/modem_simulator/misc_service.h
+++ b/host/commands/modem_simulator/misc_service.h
@@ -17,6 +17,8 @@
#include "host/commands/modem_simulator/modem_service.h"
+#include <ctime>
+
namespace cuttlefish {
class MiscService : public ModemService, public std::enable_shared_from_this<MiscService> {
@@ -29,6 +31,7 @@
MiscService &operator=(const MiscService &) = delete;
void HandleGetIMEI(const Client& client, std::string& command);
+ void HandleTimeUpdate(const Client& client, std::string& command);
void TimeUpdate();
@@ -36,6 +39,7 @@
private:
void ParseTimeZone();
+ long TimeZoneOffset(time_t* utctime); // in seconds.
void FixTimeZone(std::string& line);
std::string timezone_;
std::vector<CommandHandler> InitializeCommandHandlers();
diff --git a/host/commands/modem_simulator/modem_service.cpp b/host/commands/modem_simulator/modem_service.cpp
index 062da6f..7996e31 100644
--- a/host/commands/modem_simulator/modem_service.cpp
+++ b/host/commands/modem_simulator/modem_service.cpp
@@ -137,11 +137,8 @@
}
}
-std::string ModemService::GetHostPort() {
- auto host_port = cuttlefish::modem::DeviceConfig::host_port();
- std::stringstream ss;
- ss << host_port;
- return ss.str();
+std::string ModemService::GetHostId() {
+ return std::to_string(cuttlefish::modem::DeviceConfig::host_id());
}
} // namespace cuttlefish
diff --git a/host/commands/modem_simulator/modem_service.h b/host/commands/modem_simulator/modem_service.h
index 0b75d7d..744f618 100644
--- a/host/commands/modem_simulator/modem_service.h
+++ b/host/commands/modem_simulator/modem_service.h
@@ -104,7 +104,7 @@
void SendCommandToRemote(cuttlefish::SharedFD remote_client,
std::string response);
void CloseRemoteConnection(cuttlefish::SharedFD remote_client);
- static std::string GetHostPort();
+ static std::string GetHostId();
int32_t service_id_;
const std::vector<CommandHandler> command_handlers_;
diff --git a/host/commands/modem_simulator/modem_simulator.cpp b/host/commands/modem_simulator/modem_simulator.cpp
index 61a23bd..71c9ada 100644
--- a/host/commands/modem_simulator/modem_simulator.cpp
+++ b/host/commands/modem_simulator/modem_simulator.cpp
@@ -146,7 +146,7 @@
bool ModemSimulator::IsWaitingSmsPdu() {
if (sms_service_) {
- return (sms_service_->IsWaitingSmsPdu() |
+ return (sms_service_->IsWaitingSmsPdu() ||
sms_service_->IsWaitingSmsToSim());
}
return false;
diff --git a/host/commands/modem_simulator/network_service.cpp b/host/commands/modem_simulator/network_service.cpp
index 56f14e5..4d102fa 100644
--- a/host/commands/modem_simulator/network_service.cpp
+++ b/host/commands/modem_simulator/network_service.cpp
@@ -31,20 +31,12 @@
// string type; four byte GERAN/UTRAN cell ID in hexadecimal format
static const std::string kCellId = "0000B804";
-// Check SignalStrength.java file for more details on how these map to
-// signal strength bars
-const std::pair<int, int> kGSMSignalStrength = std::make_pair(4, 30);
-const std::pair<int, int> kCDMASignalStrength = std::make_pair(4, 120);
-const std::pair<int, int> kEVDOSignalStrength = std::make_pair(4, 120);
-const std::pair<int, int> kLTESignalStrength = std::make_pair(4, 30);
-const std::pair<int, int> kWCDMASignalStrength = std::make_pair(4, 30);
-const std::pair<int, int> kNRSignalStrength = std::make_pair(45, 135);
-
NetworkService::NetworkService(int32_t service_id,
ChannelMonitor* channel_monitor,
ThreadLooper* thread_looper)
: ModemService(service_id, this->InitializeCommandHandlers(),
- channel_monitor, thread_looper) {
+ channel_monitor, thread_looper),
+ keep_signal_strength_changing_loop_(*this) {
InitializeServiceState();
}
@@ -142,6 +134,8 @@
first_signal_strength_request_ = true;
android_last_signal_time_ = 0;
+
+ keep_signal_strength_changing_loop_.Start();
}
void NetworkService::InitializeNetworkOperator() {
@@ -255,9 +249,10 @@
// Note: not saved to nvram config due to sim status may change after reboot
current_network_mode_ = M_MODEM_TECH_WCDMA;
}
- thread_looper_->PostWithDelay(std::chrono::seconds(1),
+ thread_looper_->Post(
makeSafeCallback(this, &NetworkService::UpdateRegisterState,
- voice_registration_status_.registration_state));
+ voice_registration_status_.registration_state),
+ std::chrono::seconds(1));
}
/**
@@ -318,7 +313,6 @@
client.SendCommandResponse(kCmeErrorOperationNotSupported);
return;
}
- signal_strength_.Reset();
client.SendCommandResponse("OK");
}
@@ -335,39 +329,87 @@
return wakeup_from_sleep;
}
-void NetworkService::SetSignalStrengthValue(int& value,
- const std::pair<int, int>& range,
- double percentd) {
- value = range.first + percentd * (range.second - range.first);
- AdjustSignalStrengthValue(value, range);
-}
-
-void NetworkService::AdjustSignalStrengthValue(int& value,
- const std::pair<int, int>& range) {
- if (value < range.first) {
- value = range.first;
- } else if (value > range.second) {
- value = range.second;
- }
-}
/**
+ * IMPORTANT NOTE: Current implementation of AT+CSQ differs from standards
+ * described in TS 27.007 8.5 which only only supports RSSI and BER.
+ *
+ * TODO(b/206814247): Rename AT+CSQ command.
+ *
* AT+CSQ
- * Execution command returns received signal strength indication <rssi>
- * and channel bit error rate <ber> from the MT.
+ * Execution command returns received signal strength indication. This is a
+ * Cuttlefish specific command.
*
- * command Possible response(s)
- * AT+CSQ +CSQ: <rssi>,<ber>
- * +CME ERROR: <err>
+ * Command Possible response(s)
+ * AT+CSQ +CSQ: <gsm_rssi>,<gsm_ber>,<cdma_dbm>,
+ * <cdma_ecio>,<evdo_dbm>,<evdo_ecio>,<evdo_snr>,
+ * <lte_rssi>,<lte_rsrp>,<lte_rsrq>,<lte_rssnr>,
+ * <lte_cqi>,<lte_ta>,<tdscdma_rscp>,<wcdma_rssi>,
+ * <wcdma_ber>,<nr_ss_rsrp>,<nr_ss_rsrq>,<nr_ss_sinr>,
+ * <nr_csi_rsrp>,<nr_csi_rsrq>,<nr_csi_sinr>
+ * +CME ERROR: <err>
*
- * <rssi>: integer type
- * 0 ‑113 dBm or less
- * 1 ‑111 dBm
- * 2...30 ‑109... ‑53 dBm
- * 31 ‑51 dBm or greater
- * 99 not known or not detectable
- * <ber>: integer type; channel bit error rate (in percent)
- * 0...7 as RXQUAL values in the table in 3GPP TS 45.008 [20] subclause 8.2.4
- * 99 not known or not detectable
+ * <gsm_rssi>: Valid values are (0-31, 99) as defined in TS 27.007 8.5.
+ * <gsm_ber>: Bit error rate (0-7, 99) as defined in TS 27.007 8.5.
+ * <cdma_dbm>: Valid values are positive integers.
+ * This value is the actual RSSI value multiplied by -1.
+ * Example: If the actual RSSI is -75, then this response value will be 75.
+ * <cdma_ecio>: Valid values are positive integers.
+ * This value is the actual Ec/Io multiplied by -10.
+ * Example: If the actual Ec/Io is -12.5 dB, then this response value will
+ * be 125.
+ * <evdo_dbm>: Refer cdma_dbm.
+ * <evdo_ecio>: Refer cdma_ecio.
+ * <evdo_snr>: Valid values are 0-8.
+ * 8 is the highest signal to noise ratio.
+ * <lte_rssi>: Refer gsm_rssi.
+ * <lte_rsrp>:
+ * The current Reference Signal Receive Power in dBm multiplied by -1.
+ * Range: 44 to 140 dBm.
+ * INT_MAX: 0x7FFFFFFF denotes invalid value.
+ * Reference: 3GPP TS 36.133 9.1.4.
+ * <lte_rsrq>:
+ * The current Reference Signal Receive Quality in dB multiplied by -1.
+ * Range: 20 to 3 dB.
+ * INT_MAX: 0x7FFFFFFF denotes invalid value.
+ * Reference: 3GPP TS 36.133 9.1.7.
+ * <lte_rssnr>:
+ * The current reference signal signal-to-noise ratio in 0.1 dB units.
+ * Range: -200 to +300 (-200 = -20.0 dB, +300 = 30dB).
+ * INT_MAX : 0x7FFFFFFF denotes invalid value.
+ * Reference: 3GPP TS 36.101 8.1.1.
+ * <lte_cqi>: The current Channel Quality Indicator.
+ * Range: 0 to 15.
+ * INT_MAX : 0x7FFFFFFF denotes invalid value.
+ * Reference: 3GPP TS 36.101 9.2, 9.3, A.4.
+ * <lte_ta>:
+ * Timing advance in micro seconds for a one way trip from cell to device.
+ * Approximate distance can be calculated using 300m/us * timingAdvance.
+ * Range: 0 to 0x7FFFFFFE.
+ * INT_MAX : 0x7FFFFFFF denotes invalid value.
+ * Reference: 3GPP 36.321 section 6.1.3.5.
+ * <tdscdma_rscp>: P-CCPCH RSCP as defined in TS 25.225 5.1.1.
+ * Valid values are (0-96, 255) as defined in TS 27.007 8.69.
+ * INT_MAX denotes that the value is invalid/unreported.
+ * <wcdma_rssi>: Refer gsm_rssi.
+ * <wcdma_ber>: Refer gsm_ber.
+ * <nr_ss_rsrp>: SS reference signal received power, multiplied by -1.
+ * Reference: 3GPP TS 38.215.
+ * Range [44, 140], INT_MAX means invalid/unreported.
+ * <nr_ss_rsrq>: SS reference signal received quality, multiplied by -1.
+ * Reference: 3GPP TS 38.215.
+ * Range [3, 20], INT_MAX means invalid/unreported.
+ * <nr_ss_sinr>: SS signal-to-noise and interference ratio.
+ * Reference: 3GPP TS 38.215 section 5.1.*, 3GPP TS 38.133 section 10.1.16.1.
+ * Range [-23, 40], INT_MAX means invalid/unreported.
+ * <nr_csi_rsrp>: CSI reference signal received power, multiplied by -1.
+ * Reference: 3GPP TS 38.215.
+ * Range [44, 140], INT_MAX means invalid/unreported.
+ * <nr_csi_rsrq>: CSI reference signal received quality, multiplied by -1.
+ * Reference: 3GPP TS 38.215.
+ * Range [3, 20], INT_MAX means invalid/unreported.
+ * <nr_csi_sinr>: CSI signal-to-noise and interference ratio.
+ * Reference: 3GPP TS 138.215 section 5.1.*, 3GPP TS 38.133 section 10.1.16.1.
+ * Range [-23, 40], INT_MAX means invalid/unreported.
*
* see RIL_REQUEST_SIGNAL_STRENGTH in RIL
*/
@@ -384,7 +426,7 @@
android_last_signal_time_ = time(0);
- auto response = GetSignalStrength();
+ auto response = BuildCSQCommandResponse(GetCurrentSignalStrength());
responses.push_back(response);
responses.push_back("OK");
@@ -392,11 +434,9 @@
}
bool NetworkService::IsHasNetwork() {
- if (radio_state_ == RADIO_STATE_OFF ||
- oper_selection_mode_ == OperatorSelectionMode::OPER_SELECTION_DEREGISTRATION) {
- return false;
- }
- return true;
+ return radio_state_ != RADIO_STATE_OFF &&
+ oper_selection_mode_ !=
+ OperatorSelectionMode::OPER_SELECTION_DEREGISTRATION;
}
/**
@@ -676,8 +716,10 @@
NvramConfig::SaveToFile();
- thread_looper_->PostWithDelay(std::chrono::seconds(1),
- makeSafeCallback(this, &NetworkService::UpdateRegisterState, registration_state));
+ thread_looper_->Post(
+ makeSafeCallback(this, &NetworkService::UpdateRegisterState,
+ registration_state),
+ std::chrono::seconds(1));
}
NetworkService::NetworkRegistrationStatus::AccessTechnoloy
@@ -987,13 +1029,13 @@
if (current != current_network_mode_) {
UpdateRegisterState(NET_REGISTRATION_UNREGISTERED);
- signal_strength_.Reset();
ss << "+CTEC: "<< current_network_mode_;
- thread_looper_->PostWithDelay(std::chrono::milliseconds(200),
+ thread_looper_->Post(
makeSafeCallback(this, &NetworkService::UpdateRegisterState,
- NET_REGISTRATION_HOME));
+ NET_REGISTRATION_HOME),
+ std::chrono::milliseconds(200));
} else {
ss << "+CTEC: DONE";
}
@@ -1071,60 +1113,75 @@
SendUnsolicitedCommand(ss.str());
}
-std::string NetworkService::GetSignalStrength() {
+int NetworkService::GetValueInRange(const std::pair<int, int>& range,
+ int percent) {
+ int range_size = range.second - range.first + 1;
+ return range.first + (int)((percent / 101.0) * range_size);
+}
+
+std::string NetworkService::BuildCSQCommandResponse(
+ const SignalStrength& signal_strength) {
+ std::stringstream ss;
+ // clang-format off
+ ss << "+CSQ: "
+ << signal_strength.gsm_rssi << ","
+ << signal_strength.gsm_ber << ","
+ << signal_strength.cdma_dbm << ","
+ << signal_strength.cdma_ecio << ","
+ << signal_strength.evdo_dbm << ","
+ << signal_strength.evdo_ecio << ","
+ << signal_strength.evdo_snr << ","
+ << signal_strength.lte_rssi << ","
+ << signal_strength.lte_rsrp << ","
+ << signal_strength.lte_rsrq << ","
+ << signal_strength.lte_rssnr << ","
+ << signal_strength.lte_cqi << ","
+ << signal_strength.lte_ta << ","
+ << signal_strength.tdscdma_rscp << ","
+ << signal_strength.wcdma_rssi << ","
+ << signal_strength.wcdma_ber << ","
+ << signal_strength.nr_ss_rsrp << ","
+ << signal_strength.nr_ss_rsrq << ","
+ << signal_strength.nr_ss_sinr << ","
+ << signal_strength.nr_csi_rsrp << ","
+ << signal_strength.nr_csi_rsrq << ","
+ << signal_strength.nr_csi_sinr;
+ // clang-format on
+ return ss.str();
+}
+
+NetworkService::SignalStrength NetworkService::GetCurrentSignalStrength() {
+ NetworkService::SignalStrength result;
+ if (!IsHasNetwork()) {
+ return result;
+ }
+ int percent = signal_strength_percent_;
switch (current_network_mode_) {
case M_MODEM_TECH_GSM:
- signal_strength_.gsm_rssi += (rand() % 3 - 1);
- AdjustSignalStrengthValue(signal_strength_.gsm_rssi, kGSMSignalStrength);
+ result.gsm_rssi = GetValueInRange(kRssiRange, percent);
break;
case M_MODEM_TECH_CDMA:
- signal_strength_.cdma_dbm += (rand() % 3 - 1);
- AdjustSignalStrengthValue(signal_strength_.cdma_dbm, kCDMASignalStrength);
+ result.cdma_dbm = GetValueInRange(kDbmRange, percent) * -1;
break;
case M_MODEM_TECH_EVDO:
- signal_strength_.evdo_dbm += (rand() % 3 - 1);
- AdjustSignalStrengthValue(signal_strength_.evdo_dbm, kEVDOSignalStrength);
+ result.evdo_dbm = GetValueInRange(kDbmRange, percent) * -1;
break;
case M_MODEM_TECH_LTE:
- signal_strength_.lte_rssi += (rand() % 3 - 1);
- AdjustSignalStrengthValue(signal_strength_.lte_rssi, kLTESignalStrength);
+ result.lte_rsrp = GetValueInRange(kRsrpRange, percent) * -1;
break;
case M_MODEM_TECH_WCDMA:
- signal_strength_.wcdma_rssi += (rand() % 3 - 1);
- AdjustSignalStrengthValue(signal_strength_.wcdma_rssi, kWCDMASignalStrength);
+ result.wcdma_rssi = GetValueInRange(kRssiRange, percent);
break;
case M_MODEM_TECH_NR:
- signal_strength_.nr_ss_rsrp += (rand() % 3 - 1);
- AdjustSignalStrengthValue(signal_strength_.nr_ss_rsrp, kNRSignalStrength);
+ // special for NR: it uses LTE as primary, so LTE signal strength is
+ // needed as well
+ result.lte_rsrp = GetValueInRange(kRsrpRange, percent) * -1;
+ result.nr_ss_rsrp = GetValueInRange(kRsrpRange, percent) * -1;
break;
default:
break;
}
-
- std::stringstream ss;
- ss << "+CSQ: " << signal_strength_.gsm_rssi << ","
- << signal_strength_.gsm_ber << ","
- << signal_strength_.cdma_dbm << ","
- << signal_strength_.cdma_ecio << ","
- << signal_strength_.evdo_dbm << ","
- << signal_strength_.evdo_ecio << ","
- << signal_strength_.evdo_snr << ","
- << signal_strength_.lte_rssi << ","
- << signal_strength_.lte_rsrp << ","
- << signal_strength_.lte_rsrq << ","
- << signal_strength_.lte_rssnr << ","
- << signal_strength_.lte_cqi << ","
- << signal_strength_.lte_ta << ","
- << signal_strength_.tdscdma_rscp << ","
- << signal_strength_.wcdma_rssi << ","
- << signal_strength_.wcdma_ber << ","
- << signal_strength_.nr_ss_rsrp << ","
- << signal_strength_.nr_ss_rsrq << ","
- << signal_strength_.nr_ss_sinr << ","
- << signal_strength_.nr_csi_rsrp << ","
- << signal_strength_.nr_csi_rsrq << ","
- << signal_strength_.nr_csi_sinr;;
- return ss.str();
+ return result;
}
/* AT+REMOTEREG: state*/
@@ -1136,12 +1193,11 @@
int stated = std::stoi(states, nullptr, 10);
UpdateRegisterState(NET_REGISTRATION_UNREGISTERED);
- signal_strength_.Reset();
- thread_looper_->PostWithDelay(
- std::chrono::seconds(1),
+ thread_looper_->Post(
makeSafeCallback(this, &NetworkService::UpdateRegisterState,
- (cuttlefish::NetworkService::RegistrationState)stated));
+ (cuttlefish::NetworkService::RegistrationState)stated),
+ std::chrono::seconds(1));
}
/* AT+REMOTECTEC: ctec */
@@ -1162,77 +1218,13 @@
current_network_mode_ = current_network_mode_new;
auto saved_state = voice_registration_status_.registration_state;
UpdateRegisterState(NET_REGISTRATION_UNREGISTERED);
- signal_strength_.Reset();
ss << "+CTEC: " << current_network_mode_;
- thread_looper_->PostWithDelay(
- std::chrono::seconds(1),
+ thread_looper_->Post(
makeSafeCallback(this, &NetworkService::UpdateRegisterState,
- saved_state));
- }
-}
-
-void NetworkService::applySignalPercentage(double percentd) {
- switch (current_network_mode_) {
- case M_MODEM_TECH_GSM:
- signal_strength_.gsm_rssi = 99;
- signal_strength_.gsm_ber = 0;
- SetSignalStrengthValue(signal_strength_.gsm_rssi, kGSMSignalStrength,
- percentd);
- break;
- case M_MODEM_TECH_CDMA:
- signal_strength_.cdma_dbm = 125;
- signal_strength_.cdma_ecio = 165;
- SetSignalStrengthValue(signal_strength_.cdma_dbm, kCDMASignalStrength,
- percentd);
- break;
- case M_MODEM_TECH_EVDO:
- signal_strength_.evdo_dbm = 125;
- signal_strength_.evdo_ecio = 165;
- signal_strength_.evdo_snr = -1;
- SetSignalStrengthValue(signal_strength_.evdo_dbm, kEVDOSignalStrength,
- percentd);
- break;
- case M_MODEM_TECH_LTE:
- signal_strength_.lte_rssi = 99;
- signal_strength_.lte_rsrp = -1;
- signal_strength_.lte_rsrq = -5;
- signal_strength_.lte_rssnr = -205;
- signal_strength_.lte_cqi = -1;
- signal_strength_.lte_ta = -1;
- SetSignalStrengthValue(signal_strength_.lte_rssi, kLTESignalStrength,
- percentd);
- break;
- case M_MODEM_TECH_WCDMA:
- signal_strength_.tdscdma_rscp = 99;
- signal_strength_.wcdma_rssi = 99;
- signal_strength_.wcdma_ber = 0;
- SetSignalStrengthValue(signal_strength_.wcdma_rssi, kWCDMASignalStrength,
- percentd);
- break;
- case M_MODEM_TECH_NR:
- // special for NR: it uses LTE as primary, so LTE signal strength is
- // needed as well
- signal_strength_.lte_rssi = 99;
- signal_strength_.lte_rsrp = -1;
- signal_strength_.lte_rsrq = -5;
- signal_strength_.lte_rssnr = -205;
- signal_strength_.lte_cqi = -1;
- signal_strength_.lte_ta = -1;
- SetSignalStrengthValue(signal_strength_.lte_rssi, kLTESignalStrength,
- percentd);
- signal_strength_.nr_ss_rsrp = 0;
- signal_strength_.nr_ss_rsrq = 0;
- signal_strength_.nr_ss_sinr = 45;
- signal_strength_.nr_csi_rsrp = 0;
- signal_strength_.nr_csi_rsrq = 0;
- signal_strength_.nr_csi_sinr = 30;
- SetSignalStrengthValue(signal_strength_.nr_ss_rsrp, kNRSignalStrength,
- percentd);
- break;
- default:
- break;
+ saved_state),
+ std::chrono::seconds(1));
}
}
@@ -1242,12 +1234,12 @@
(void)client;
std::stringstream ss;
std::string percents = command.substr(std::string("AT+REMOTESIGNAL:").size());
- double percentd = std::stoi(percents, nullptr, 10) / 100.0;
+ int percent = std::stoi(percents, nullptr, 10);
- if (percentd >= 0 && percentd <= 1.0) {
- percentd_ = percentd;
+ if (percent >= 0 && percent <= 100) {
+ signal_strength_percent_ = percent;
} else {
- LOG(DEBUG) << "out of bound signal strength: " << percentd;
+ LOG(DEBUG) << "out of bound signal strength percent: " << percent;
return;
}
@@ -1255,12 +1247,42 @@
}
void NetworkService::OnSignalStrengthChanged() {
- applySignalPercentage(percentd_);
- auto command = GetSignalStrength();
- SendUnsolicitedCommand(command);
+ SendUnsolicitedCommand(BuildCSQCommandResponse(GetCurrentSignalStrength()));
}
NetworkService::RegistrationState NetworkService::GetVoiceRegistrationState() const {
return voice_registration_status_.registration_state;
}
+
+NetworkService::KeepSignalStrengthChangingLoop::KeepSignalStrengthChangingLoop(
+ NetworkService& network_service)
+ : network_service_{network_service}, loop_started_ ATOMIC_FLAG_INIT {}
+
+void NetworkService::KeepSignalStrengthChangingLoop::Start() {
+ if (loop_started_.test_and_set()) {
+ LOG(ERROR) << "Signal strength is already changing automatically";
+ } else {
+ UpdateSignalStrengthCallback();
+ }
+}
+
+void NetworkService::KeepSignalStrengthChangingLoop::
+ UpdateSignalStrengthCallback() {
+ if (network_service_.IsHasNetwork()) {
+ network_service_.signal_strength_percent_ -= 5;
+ // With "close to 0" values, the signal strength bar on the Android UI will
+ // be shown empty, this also represents that theres's no connectivity which
+ // is missleading as the connectivity continues, so a lower bound of 10 will
+ // be used so the signal strenght bar is never emptied
+ if (network_service_.signal_strength_percent_ <= 10) {
+ network_service_.signal_strength_percent_ = 100;
+ }
+ network_service_.OnSignalStrengthChanged();
+ }
+ network_service_.thread_looper_->Post(
+ makeSafeCallback(this, &NetworkService::KeepSignalStrengthChangingLoop::
+ UpdateSignalStrengthCallback),
+ std::chrono::seconds(10));
+}
+
} // namespace cuttlefish
diff --git a/host/commands/modem_simulator/network_service.h b/host/commands/modem_simulator/network_service.h
index b64d4b9..37808fb 100644
--- a/host/commands/modem_simulator/network_service.h
+++ b/host/commands/modem_simulator/network_service.h
@@ -20,6 +20,7 @@
#include "host/commands/modem_simulator/data_service.h"
#include "host/commands/modem_simulator/misc_service.h"
#include "host/commands/modem_simulator/modem_service.h"
+#include "host/commands/modem_simulator/network_service_constants.h"
#include "host/commands/modem_simulator/sim_service.h"
namespace cuttlefish {
@@ -82,10 +83,6 @@
bool IsHasNetwork();
void UpdateRegisterState(RegistrationState state);
void AdjustSignalStrengthValue(int& value, const std::pair<int, int>& range);
- void SetSignalStrengthValue(int& value, const std::pair<int, int>& range,
- double percentd);
- std::string GetSignalStrength();
- void applySignalPercentage(double percentd);
MiscService* misc_service_ = nullptr;
SimService* sim_service_ = nullptr;
@@ -202,61 +199,41 @@
* Reference: 3GPP TS 138.215 section 5.1.*, 3GPP TS 38.133 section 10.1.16.1.
* Range [-23, 40], INT_MAX means invalid/unreported. */
- // Default invalid value
- SignalStrength():
- gsm_rssi(99), // [0, 31]
- gsm_ber(0), // [7, 99]
- cdma_dbm(125), // [0, 120]
- cdma_ecio(165), // [0, 160]
- evdo_dbm(125), // [0, 120]
- evdo_ecio(165), // [0, 160]
- evdo_snr(-1), // [0, 8]
- lte_rssi(99), // [0, 31]
- lte_rsrp(-1), // [43,140]
- lte_rsrq(-5), // [-3,34]
- lte_rssnr(-205), // [-200, 300]
- lte_cqi(-1), // [0, 15]
- lte_ta(-1), // [0, 1282]
- tdscdma_rscp(99), // [0, 96]
- wcdma_rssi(99), // [0, 31]
- wcdma_ber(0), // [7, 99]
- nr_ss_rsrp(0), // [44, 140]
- nr_ss_rsrq(0), // [3, 10]
- nr_ss_sinr(45), // [-23,40]
- nr_csi_rsrp(0), // [44, 140]
- nr_csi_rsrq(0), // [3, 20]
- nr_csi_sinr(30) // [-23, 23]
- {}
-
- // After radio power on, off, or set network mode, reset to invalid value
- void Reset() {
- gsm_rssi = INT_MAX;
- gsm_ber = INT_MAX;
- cdma_dbm = INT_MAX;
- cdma_ecio = INT_MAX;
- evdo_dbm = INT_MAX;
- evdo_ecio = INT_MAX;
- evdo_snr = INT_MAX;
- lte_rssi = INT_MAX;
- lte_rsrp = INT_MAX;
- lte_rsrq = INT_MAX;
- lte_rssnr = INT_MAX;
- lte_cqi = INT_MAX;
- lte_ta = INT_MAX;
- tdscdma_rscp = INT_MAX;
- wcdma_rssi = INT_MAX;
- wcdma_ber = INT_MAX;
- nr_ss_rsrp = INT_MAX;
- nr_ss_rsrq = INT_MAX;
- nr_ss_sinr = INT_MAX;
- nr_csi_rsrp = INT_MAX;
- nr_csi_rsrq = INT_MAX;
- nr_csi_sinr = INT_MAX;
- }
+ SignalStrength()
+ : gsm_rssi(kRssiUnknownValue),
+ gsm_ber(kBerUnknownValue),
+ cdma_dbm(kDbmUnknownValue),
+ cdma_ecio(kEcioUnknownValue),
+ evdo_dbm(kDbmUnknownValue),
+ evdo_ecio(kEcioUnknownValue),
+ evdo_snr(kSnrUnknownValue),
+ lte_rssi(kRssiUnknownValue),
+ lte_rsrp(INT_MAX),
+ lte_rsrq(INT_MAX),
+ lte_rssnr(INT_MAX),
+ lte_cqi(INT_MAX),
+ lte_ta(INT_MAX),
+ tdscdma_rscp(INT_MAX),
+ wcdma_rssi(kRssiUnknownValue),
+ wcdma_ber(kBerUnknownValue),
+ nr_ss_rsrp(INT_MAX),
+ nr_ss_rsrq(INT_MAX),
+ nr_ss_sinr(INT_MAX),
+ nr_csi_rsrp(INT_MAX),
+ nr_csi_rsrq(INT_MAX),
+ nr_csi_sinr(INT_MAX) {}
};
- double percentd_{0.8};
- SignalStrength signal_strength_;
+ // There's no such thing as a percentange for signal strength in the real
+ // world, as for example for battery usage, this percent value is used to pick
+ // a value within the corresponding signal strength values range for emulation
+ // purposes only.
+ int signal_strength_percent_{80};
+
+ static int GetValueInRange(const std::pair<int, int>& range, int percent);
+ static std::string BuildCSQCommandResponse(
+ const SignalStrength& signal_strength);
+ SignalStrength GetCurrentSignalStrength();
/* Data / voice Registration State */
struct NetworkRegistrationStatus {
@@ -315,6 +292,20 @@
bool first_signal_strength_request_; // For time update
time_t android_last_signal_time_;
+
+ class KeepSignalStrengthChangingLoop {
+ public:
+ KeepSignalStrengthChangingLoop(NetworkService& network_service);
+ void Start();
+
+ private:
+ void UpdateSignalStrengthCallback();
+
+ NetworkService& network_service_;
+ std::atomic_flag loop_started_;
+ };
+
+ KeepSignalStrengthChangingLoop keep_signal_strength_changing_loop_;
};
} // namespace cuttlefish
diff --git a/host/commands/modem_simulator/network_service_constants.h b/host/commands/modem_simulator/network_service_constants.h
new file mode 100644
index 0000000..49d0319
--- /dev/null
+++ b/host/commands/modem_simulator/network_service_constants.h
@@ -0,0 +1,34 @@
+//
+// Copyright (C) 2021 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#pragma once
+
+namespace cuttlefish {
+
+// Constants representing a not known or not detectable value for different
+// signal strength parameters.
+constexpr int kRssiUnknownValue = 99;
+constexpr int kBerUnknownValue = 99;
+constexpr int kDbmUnknownValue = -1;
+constexpr int kEcioUnknownValue = -1;
+constexpr int kSnrUnknownValue = -1;
+
+// Constants representing the range of values of different signal strength
+// parameters.
+constexpr auto kRssiRange = std::make_pair(4, 30);
+constexpr auto kDbmRange = std::make_pair(-120, -4);
+constexpr auto kRsrpRange = std::make_pair(-140, -44);
+
+} // namespace cuttlefish
diff --git a/host/commands/modem_simulator/nvram_config.cpp b/host/commands/modem_simulator/nvram_config.cpp
index 7efb4c9..f61f96f 100644
--- a/host/commands/modem_simulator/nvram_config.cpp
+++ b/host/commands/modem_simulator/nvram_config.cpp
@@ -115,7 +115,7 @@
}
Json::CharReaderBuilder builder;
- std::ifstream ifs(real_file_path);
+ std::ifstream ifs = modem::DeviceConfig::open_ifstream_crossplat(real_file_path.c_str());
std::string errorMessage;
if (!Json::parseFromStream(builder, ifs, dictionary_.get(), &errorMessage)) {
LOG(ERROR) << "Could not read config file " << file << ": "
@@ -126,7 +126,7 @@
}
bool NvramConfig::SaveToFile(const std::string& file) const {
- std::ofstream ofs(file);
+ std::ofstream ofs = modem::DeviceConfig::open_ofstream_crossplat(file.c_str());
if (!ofs.is_open()) {
LOG(ERROR) << "Unable to write to file " << file;
return false;
diff --git a/host/commands/modem_simulator/sms_service.cpp b/host/commands/modem_simulator/sms_service.cpp
index 0da21a6..0caee16 100644
--- a/host/commands/modem_simulator/sms_service.cpp
+++ b/host/commands/modem_simulator/sms_service.cpp
@@ -256,8 +256,8 @@
return;
}
- auto local_host_port = GetHostPort();
- auto pdu = sms_pdu.CreateRemotePDU(local_host_port);
+ auto local_host_id = GetHostId();
+ auto pdu = sms_pdu.CreateRemotePDU(local_host_id);
std::string command = "AT+REMOTESMS=" + pdu + "\r";
std::string token = "REM0";
@@ -292,25 +292,22 @@
return;
} else if (port >= kRemotePortRange.first &&
port <= kRemotePortRange.second) {
- std::stringstream ss;
- ss << port;
- auto remote_host_port = ss.str();
- if (GetHostPort() == remote_host_port) { // Send SMS to local host port
- thread_looper_->PostWithDelay(
- std::chrono::seconds(1),
- makeSafeCallback<SmsService>(this, [&sms_pdu](SmsService* me) {
- me->HandleReceiveSMS(sms_pdu);
- }));
+ auto remote_host_port = std::to_string(port);
+ if (GetHostId() == remote_host_port) { // Send SMS to local host port
+ thread_looper_->Post(
+ makeSafeCallback<SmsService>(
+ this,
+ [&sms_pdu](SmsService* me) { me->HandleReceiveSMS(sms_pdu); }),
+ std::chrono::seconds(1));
} else { // Send SMS to remote host port
SendSmsToRemote(remote_host_port, sms_pdu);
}
} else if (sim_service_ && phone_number == sim_service_->GetPhoneNumber()) {
/* Local phone number */
- thread_looper_->PostWithDelay(
- std::chrono::seconds(1),
- makeSafeCallback<SmsService>(this, [sms_pdu](SmsService* me) {
- me->HandleReceiveSMS(sms_pdu);
- }));
+ thread_looper_->Post(
+ makeSafeCallback<SmsService>(
+ this, [sms_pdu](SmsService* me) { me->HandleReceiveSMS(sms_pdu); }),
+ std::chrono::seconds(1));
} /* else pretend send SMS success */
std::stringstream ss;
@@ -321,11 +318,12 @@
if (sms_pdu.IsNeededStatuReport()) {
int ref = message_reference_;
- thread_looper_->PostWithDelay(
- std::chrono::seconds(1),
- makeSafeCallback<SmsService>(this, [sms_pdu, ref](SmsService* me) {
- me->HandleSMSStatuReport(sms_pdu, ref);
- }));
+ thread_looper_->Post(
+ makeSafeCallback<SmsService>(this,
+ [sms_pdu, ref](SmsService* me) {
+ me->HandleSMSStatuReport(sms_pdu, ref);
+ }),
+ std::chrono::seconds(1));
}
}
diff --git a/host/commands/modem_simulator/thread_looper.cpp b/host/commands/modem_simulator/thread_looper.cpp
index d9df9df..cc6c39e 100644
--- a/host/commands/modem_simulator/thread_looper.cpp
+++ b/host/commands/modem_simulator/thread_looper.cpp
@@ -42,8 +42,8 @@
return serial;
}
-ThreadLooper::Serial ThreadLooper::PostWithDelay(
- std::chrono::steady_clock::duration delay, Callback cb) {
+ThreadLooper::Serial ThreadLooper::Post(
+ Callback cb, std::chrono::steady_clock::duration delay) {
CHECK(cb != nullptr);
auto serial = next_serial_++;
diff --git a/host/commands/modem_simulator/thread_looper.h b/host/commands/modem_simulator/thread_looper.h
index dbfd201..dcc4efb 100644
--- a/host/commands/modem_simulator/thread_looper.h
+++ b/host/commands/modem_simulator/thread_looper.h
@@ -60,7 +60,7 @@
typedef int32_t Serial;
Serial Post(Callback cb);
- Serial PostWithDelay(std::chrono::steady_clock::duration delay, Callback cb);
+ Serial Post(Callback cb, std::chrono::steady_clock::duration delay);
void Stop();
diff --git a/host/commands/modem_simulator/unittest/service_test.cpp b/host/commands/modem_simulator/unittest/service_test.cpp
index 5801586..4bcbcb0 100644
--- a/host/commands/modem_simulator/unittest/service_test.cpp
+++ b/host/commands/modem_simulator/unittest/service_test.cpp
@@ -23,6 +23,7 @@
#include "common/libs/fs/shared_select.h"
#include "common/libs/utils/files.h"
#include "host/commands/modem_simulator/channel_monitor.h"
+#include "host/commands/modem_simulator/device_config.h"
#include "host/commands/modem_simulator/modem_simulator.h"
#include "host/libs/config/cuttlefish_config.h"
namespace fs = std::filesystem;
@@ -41,31 +42,31 @@
{
cuttlefish::CuttlefishConfig tmp_config_obj;
std::string config_file = tmp_test_dir + "/.cuttlefish_config.json";
- std::string instance_dir = tmp_test_dir + "/cuttlefish_runtime.1";
- fs::create_directories(instance_dir);
+ tmp_config_obj.set_root_dir(tmp_test_dir + "/cuttlefish");
tmp_config_obj.set_ril_dns("8.8.8.8");
std::vector<int> instance_nums;
for (int i = 0; i < 1; i++) {
instance_nums.push_back(cuttlefish::GetInstance() + i);
}
for (const auto &num : instance_nums) {
- auto instance = tmp_config_obj.ForInstance(num);
- instance.set_instance_dir(instance_dir);
+ tmp_config_obj.ForInstance(num); // Trigger creation in map
}
for (auto instance : tmp_config_obj.Instances()) {
+ fs::create_directories(instance.instance_dir());
if (!tmp_config_obj.SaveToFile(
instance.PerInstancePath("cuttlefish_config.json"))) {
LOG(ERROR) << "Unable to save copy config object";
return;
}
+ std::string icfilename =
+ instance.PerInstancePath("/iccprofile_for_sim0.xml");
+ std::ofstream offile = modem::DeviceConfig::open_ofstream_crossplat(icfilename.c_str(), std::ofstream::out);
+ offile << std::string(myiccfile);
+ offile.close();
+ fs::copy_file(instance.PerInstancePath("/cuttlefish_config.json"),
+ config_file, fs::copy_options::overwrite_existing);
}
- fs::copy_file(instance_dir + "/cuttlefish_config.json", config_file,
- fs::copy_options::overwrite_existing);
- std::string icfilename = instance_dir + "/iccprofile_for_sim0.xml";
- std::ofstream offile(icfilename, std::ofstream::out);
- offile << std::string(myiccfile);
- offile.close();
::setenv("CUTTLEFISH_CONFIG_FILE", config_file.c_str(), 1);
}
diff --git a/host/commands/powerwash_cvd/Android.bp b/host/commands/powerwash_cvd/Android.bp
index adaacb9..5695f8b 100644
--- a/host/commands/powerwash_cvd/Android.bp
+++ b/host/commands/powerwash_cvd/Android.bp
@@ -23,9 +23,11 @@
"powerwash_cvd.cc",
],
shared_libs: [
+ "libext2_blkid",
"libbase",
"libcuttlefish_fs",
"libcuttlefish_utils",
+ "libfruit",
"libjsoncpp",
],
static_libs: [
diff --git a/host/commands/restart_cvd/Android.bp b/host/commands/restart_cvd/Android.bp
index dc7a044..8b1b971 100644
--- a/host/commands/restart_cvd/Android.bp
+++ b/host/commands/restart_cvd/Android.bp
@@ -23,9 +23,11 @@
"restart_cvd.cc",
],
shared_libs: [
+ "libext2_blkid",
"libbase",
"libcuttlefish_fs",
"libcuttlefish_utils",
+ "libfruit",
"libjsoncpp",
],
static_libs: [
diff --git a/host/commands/restart_cvd/restart_cvd.cc b/host/commands/restart_cvd/restart_cvd.cc
index 68ff3e6..6c165f9 100644
--- a/host/commands/restart_cvd/restart_cvd.cc
+++ b/host/commands/restart_cvd/restart_cvd.cc
@@ -44,7 +44,6 @@
#include "common/libs/utils/environment.h"
#include "host/commands/run_cvd/runner_defs.h"
#include "host/libs/config/cuttlefish_config.h"
-#include "host/libs/vm_manager/vm_manager.h"
DEFINE_int32(instance_num, cuttlefish::GetInstance(),
"Which instance to restart");
diff --git a/host/commands/run_cvd/Android.bp b/host/commands/run_cvd/Android.bp
index 09b11d5..2f6eb0b 100644
--- a/host/commands/run_cvd/Android.bp
+++ b/host/commands/run_cvd/Android.bp
@@ -22,28 +22,33 @@
srcs: [
"boot_state_machine.cc",
"launch.cc",
- "launch_adb.cpp",
"launch_modem.cpp",
"launch_streamer.cpp",
"main.cc",
+ "reporting.cpp",
"process_monitor.cc",
"server_loop.cpp",
+ "validate.cpp",
],
shared_libs: [
+ "libext2_blkid",
"libcuttlefish_fs",
"libcuttlefish_utils",
"libcuttlefish_kernel_log_monitor_utils",
"libbase",
+ "libfruit",
"libjsoncpp",
"libnl",
],
static_libs: [
"libcuttlefish_host_config",
+ "libcuttlefish_host_config_adb",
"libcuttlefish_vm_manager",
"libgflags",
],
defaults: [
"cuttlefish_host",
"cuttlefish_libicuuc",
+ "cvd_cc_defaults",
],
}
diff --git a/host/commands/run_cvd/boot_state_machine.cc b/host/commands/run_cvd/boot_state_machine.cc
index 64eb893..5923631 100644
--- a/host/commands/run_cvd/boot_state_machine.cc
+++ b/host/commands/run_cvd/boot_state_machine.cc
@@ -16,33 +16,221 @@
#include "host/commands/run_cvd/boot_state_machine.h"
+#include <poll.h>
+
#include <memory>
#include <thread>
-#include "android-base/logging.h"
+#include <android-base/logging.h>
+#include <gflags/gflags.h>
+
#include "common/libs/fs/shared_fd.h"
+#include "common/libs/utils/tee_logging.h"
#include "host/commands/kernel_log_monitor/kernel_log_server.h"
#include "host/commands/kernel_log_monitor/utils.h"
#include "host/commands/run_cvd/runner_defs.h"
+#include "host/libs/config/feature.h"
+
+DEFINE_int32(reboot_notification_fd, -1,
+ "A file descriptor to notify when boot completes.");
namespace cuttlefish {
+namespace {
-CvdBootStateMachine::CvdBootStateMachine(SharedFD fg_launcher_pipe,
- SharedFD reboot_notification,
- SharedFD boot_events_pipe)
- : fg_launcher_pipe_(fg_launcher_pipe),
- reboot_notification_(reboot_notification),
- state_(kBootStarted) {
- boot_event_handler_ = std::thread([this, boot_events_pipe]() {
+// Forks and returns the write end of a pipe to the child process. The parent
+// process waits for boot events to come through the pipe and exits accordingly.
+SharedFD DaemonizeLauncher(const CuttlefishConfig& config) {
+ auto instance = config.ForDefaultInstance();
+ SharedFD read_end, write_end;
+ if (!SharedFD::Pipe(&read_end, &write_end)) {
+ LOG(ERROR) << "Unable to create pipe";
+ return {}; // a closed FD
+ }
+ auto pid = fork();
+ if (pid) {
+ // Explicitly close here, otherwise we may end up reading forever if the
+ // child process dies.
+ write_end->Close();
+ RunnerExitCodes exit_code;
+ auto bytes_read = read_end->Read(&exit_code, sizeof(exit_code));
+ if (bytes_read != sizeof(exit_code)) {
+ LOG(ERROR) << "Failed to read a complete exit code, read " << bytes_read
+ << " bytes only instead of the expected " << sizeof(exit_code);
+ exit_code = RunnerExitCodes::kPipeIOError;
+ } else if (exit_code == RunnerExitCodes::kSuccess) {
+ LOG(INFO) << "Virtual device booted successfully";
+ } else if (exit_code == RunnerExitCodes::kVirtualDeviceBootFailed) {
+ LOG(ERROR) << "Virtual device failed to boot";
+ } else {
+ LOG(ERROR) << "Unexpected exit code: " << exit_code;
+ }
+ if (exit_code == RunnerExitCodes::kSuccess) {
+ LOG(INFO) << kBootCompletedMessage;
+ } else {
+ LOG(INFO) << kBootFailedMessage;
+ }
+ std::exit(exit_code);
+ } else {
+ // The child returns the write end of the pipe
+ if (daemon(/*nochdir*/ 1, /*noclose*/ 1) != 0) {
+ LOG(ERROR) << "Failed to daemonize child process: " << strerror(errno);
+ std::exit(RunnerExitCodes::kDaemonizationError);
+ }
+ // Redirect standard I/O
+ auto log_path = instance.launcher_log_path();
+ auto log = SharedFD::Open(log_path.c_str(), O_CREAT | O_WRONLY | O_APPEND,
+ S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP);
+ if (!log->IsOpen()) {
+ LOG(ERROR) << "Failed to create launcher log file: " << log->StrError();
+ std::exit(RunnerExitCodes::kDaemonizationError);
+ }
+ ::android::base::SetLogger(
+ TeeLogger({{LogFileSeverity(), log, MetadataLevel::FULL}}));
+ auto dev_null = SharedFD::Open("/dev/null", O_RDONLY);
+ if (!dev_null->IsOpen()) {
+ LOG(ERROR) << "Failed to open /dev/null: " << dev_null->StrError();
+ std::exit(RunnerExitCodes::kDaemonizationError);
+ }
+ if (dev_null->UNMANAGED_Dup2(0) < 0) {
+ LOG(ERROR) << "Failed dup2 stdin: " << dev_null->StrError();
+ std::exit(RunnerExitCodes::kDaemonizationError);
+ }
+ if (log->UNMANAGED_Dup2(1) < 0) {
+ LOG(ERROR) << "Failed dup2 stdout: " << log->StrError();
+ std::exit(RunnerExitCodes::kDaemonizationError);
+ }
+ if (log->UNMANAGED_Dup2(2) < 0) {
+ LOG(ERROR) << "Failed dup2 seterr: " << log->StrError();
+ std::exit(RunnerExitCodes::kDaemonizationError);
+ }
+
+ read_end->Close();
+ return write_end;
+ }
+}
+
+class ProcessLeader : public SetupFeature {
+ public:
+ INJECT(ProcessLeader(const CuttlefishConfig& config)) : config_(config) {}
+
+ SharedFD ForegroundLauncherPipe() { return foreground_launcher_pipe_; }
+
+ // SetupFeature
+ std::string Name() const override { return "ProcessLeader"; }
+ bool Enabled() const override { return true; }
+
+ private:
+ std::unordered_set<SetupFeature*> Dependencies() const override { return {}; }
+ bool Setup() override {
+ /* These two paths result in pretty different process state, but both
+ * achieve the same goal of making the current process the leader of a
+ * process group, and are therefore grouped together. */
+ if (config_.run_as_daemon()) {
+ foreground_launcher_pipe_ = DaemonizeLauncher(config_);
+ if (!foreground_launcher_pipe_->IsOpen()) {
+ return false;
+ }
+ } else {
+ // Make sure the launcher runs in its own process group even when running
+ // in the foreground
+ if (getsid(0) != getpid()) {
+ int retval = setpgid(0, 0);
+ if (retval) {
+ PLOG(ERROR) << "Failed to create new process group: ";
+ return false;
+ }
+ }
+ }
+ return true;
+ }
+
+ const CuttlefishConfig& config_;
+ SharedFD foreground_launcher_pipe_;
+};
+
+// Maintains the state of the boot process, once a final state is reached
+// (success or failure) it sends the appropriate exit code to the foreground
+// launcher process
+class CvdBootStateMachine : public SetupFeature {
+ public:
+ INJECT(CvdBootStateMachine(ProcessLeader& process_leader,
+ KernelLogPipeProvider& kernel_log_pipe_provider))
+ : process_leader_(process_leader),
+ kernel_log_pipe_provider_(kernel_log_pipe_provider),
+ state_(kBootStarted) {}
+
+ ~CvdBootStateMachine() {
+ if (interrupt_fd_->IsOpen()) {
+ CHECK(interrupt_fd_->EventfdWrite(1) >= 0);
+ }
+ if (boot_event_handler_.joinable()) {
+ boot_event_handler_.join();
+ }
+ }
+
+ // SetupFeature
+ std::string Name() const override { return "CvdBootStateMachine"; }
+ bool Enabled() const override { return true; }
+
+ private:
+ std::unordered_set<SetupFeature*> Dependencies() const {
+ return {
+ static_cast<SetupFeature*>(&process_leader_),
+ static_cast<SetupFeature*>(&kernel_log_pipe_provider_),
+ };
+ }
+ bool Setup() override {
+ interrupt_fd_ = SharedFD::Event();
+ if (!interrupt_fd_->IsOpen()) {
+ LOG(ERROR) << "Failed to open eventfd: " << interrupt_fd_->StrError();
+ return false;
+ }
+ fg_launcher_pipe_ = process_leader_.ForegroundLauncherPipe();
+ if (FLAGS_reboot_notification_fd >= 0) {
+ reboot_notification_ = SharedFD::Dup(FLAGS_reboot_notification_fd);
+ if (!reboot_notification_->IsOpen()) {
+ LOG(ERROR) << "Could not dup fd given for reboot_notification_fd";
+ return false;
+ }
+ close(FLAGS_reboot_notification_fd);
+ }
+ SharedFD boot_events_pipe = kernel_log_pipe_provider_.KernelLogPipe();
+ if (!boot_events_pipe->IsOpen()) {
+ LOG(ERROR) << "Could not get boot events pipe";
+ return false;
+ }
+ boot_event_handler_ = std::thread(
+ [this, boot_events_pipe]() { ThreadLoop(boot_events_pipe); });
+ return true;
+ }
+
+ void ThreadLoop(SharedFD boot_events_pipe) {
while (true) {
- SharedFDSet fd_set;
- fd_set.Set(boot_events_pipe);
- int result = Select(&fd_set, nullptr, nullptr, nullptr);
+ std::vector<PollSharedFd> poll_shared_fd = {
+ {
+ .fd = boot_events_pipe,
+ .events = POLLIN | POLLHUP,
+ },
+ {
+ .fd = interrupt_fd_,
+ .events = POLLIN | POLLHUP,
+ }};
+ int result = SharedFD::Poll(poll_shared_fd, -1);
+ if (poll_shared_fd[1].revents & POLLIN) {
+ return;
+ }
if (result < 0) {
PLOG(FATAL) << "Failed to call Select";
return;
}
- if (!fd_set.IsSet(boot_events_pipe)) {
+ if (poll_shared_fd[0].revents & POLLHUP) {
+ LOG(ERROR) << "Failed to read a complete kernel log boot event.";
+ state_ |= kGuestBootFailed;
+ if (MaybeWriteNotification()) {
+ break;
+ }
+ }
+ if (!(poll_shared_fd[0].revents & POLLIN)) {
continue;
}
auto sent_code = OnBootEvtReceived(boot_events_pipe);
@@ -50,61 +238,73 @@
break;
}
}
- });
-}
+ }
-CvdBootStateMachine::~CvdBootStateMachine() { boot_event_handler_.join(); }
+ // Returns true if the machine is left in a final state
+ bool OnBootEvtReceived(SharedFD boot_events_pipe) {
+ std::optional<monitor::ReadEventResult> read_result =
+ monitor::ReadEvent(boot_events_pipe);
+ if (!read_result) {
+ LOG(ERROR) << "Failed to read a complete kernel log boot event.";
+ state_ |= kGuestBootFailed;
+ return MaybeWriteNotification();
+ }
-// Returns true if the machine is left in a final state
-bool CvdBootStateMachine::OnBootEvtReceived(SharedFD boot_events_pipe) {
- std::optional<monitor::ReadEventResult> read_result =
- monitor::ReadEvent(boot_events_pipe);
- if (!read_result) {
- LOG(ERROR) << "Failed to read a complete kernel log boot event.";
- state_ |= kGuestBootFailed;
+ if (read_result->event == monitor::Event::BootCompleted) {
+ LOG(INFO) << "Virtual device booted successfully";
+ state_ |= kGuestBootCompleted;
+ } else if (read_result->event == monitor::Event::BootFailed) {
+ LOG(ERROR) << "Virtual device failed to boot";
+ state_ |= kGuestBootFailed;
+ } // Ignore the other signals
+
return MaybeWriteNotification();
}
+ bool BootCompleted() const { return state_ & kGuestBootCompleted; }
+ bool BootFailed() const { return state_ & kGuestBootFailed; }
- if (read_result->event == monitor::Event::BootCompleted) {
- LOG(INFO) << "Virtual device booted successfully";
- state_ |= kGuestBootCompleted;
- } else if (read_result->event == monitor::Event::BootFailed) {
- LOG(ERROR) << "Virtual device failed to boot";
- state_ |= kGuestBootFailed;
- } // Ignore the other signals
-
- return MaybeWriteNotification();
-}
-
-bool CvdBootStateMachine::BootCompleted() const {
- return state_ & kGuestBootCompleted;
-}
-
-bool CvdBootStateMachine::BootFailed() const {
- return state_ & kGuestBootFailed;
-}
-
-void CvdBootStateMachine::SendExitCode(RunnerExitCodes exit_code, SharedFD fd) {
- fd->Write(&exit_code, sizeof(exit_code));
- // The foreground process will exit after receiving the exit code, if we try
- // to write again we'll get a SIGPIPE
- fd->Close();
-}
-
-bool CvdBootStateMachine::MaybeWriteNotification() {
- std::vector<SharedFD> fds = {reboot_notification_, fg_launcher_pipe_};
- for (auto& fd : fds) {
- if (fd->IsOpen()) {
- if (BootCompleted()) {
- SendExitCode(RunnerExitCodes::kSuccess, fd);
- } else if (state_ & kGuestBootFailed) {
- SendExitCode(RunnerExitCodes::kVirtualDeviceBootFailed, fd);
+ void SendExitCode(RunnerExitCodes exit_code, SharedFD fd) {
+ fd->Write(&exit_code, sizeof(exit_code));
+ // The foreground process will exit after receiving the exit code, if we try
+ // to write again we'll get a SIGPIPE
+ fd->Close();
+ }
+ bool MaybeWriteNotification() {
+ std::vector<SharedFD> fds = {reboot_notification_, fg_launcher_pipe_};
+ for (auto& fd : fds) {
+ if (fd->IsOpen()) {
+ if (BootCompleted()) {
+ SendExitCode(RunnerExitCodes::kSuccess, fd);
+ } else if (state_ & kGuestBootFailed) {
+ SendExitCode(RunnerExitCodes::kVirtualDeviceBootFailed, fd);
+ }
}
}
+ // Either we sent the code before or just sent it, in any case the state is
+ // final
+ return BootCompleted() || (state_ & kGuestBootFailed);
}
- // Either we sent the code before or just sent it, in any case the state is
- // final
- return BootCompleted() || (state_ & kGuestBootFailed);
+
+ ProcessLeader& process_leader_;
+ KernelLogPipeProvider& kernel_log_pipe_provider_;
+
+ std::thread boot_event_handler_;
+ SharedFD fg_launcher_pipe_;
+ SharedFD reboot_notification_;
+ SharedFD interrupt_fd_;
+ int state_;
+ static const int kBootStarted = 0;
+ static const int kGuestBootCompleted = 1 << 0;
+ static const int kGuestBootFailed = 1 << 1;
+};
+
+} // namespace
+
+fruit::Component<fruit::Required<const CuttlefishConfig, KernelLogPipeProvider>>
+bootStateMachineComponent() {
+ return fruit::createComponent()
+ .addMultibinding<SetupFeature, ProcessLeader>()
+ .addMultibinding<SetupFeature, CvdBootStateMachine>();
}
} // namespace cuttlefish
diff --git a/host/commands/run_cvd/boot_state_machine.h b/host/commands/run_cvd/boot_state_machine.h
index 0d02e72..796bb6f 100644
--- a/host/commands/run_cvd/boot_state_machine.h
+++ b/host/commands/run_cvd/boot_state_machine.h
@@ -15,39 +15,13 @@
*/
#pragma once
-#include <memory>
-#include <thread>
-
-#include "common/libs/fs/shared_fd.h"
-#include "host/commands/run_cvd/runner_defs.h"
+#include "host/commands/run_cvd/launch.h"
+#include "host/libs/config/cuttlefish_config.h"
+#include "host/libs/config/feature.h"
namespace cuttlefish {
-// Maintains the state of the boot process, once a final state is reached
-// (success or failure) it sends the appropriate exit code to the foreground
-// launcher process
-class CvdBootStateMachine {
- public:
- CvdBootStateMachine(SharedFD fg_launcher_pipe, SharedFD reboot_notification,
- SharedFD boot_events_pipe);
- ~CvdBootStateMachine();
-
- private:
- // Returns true if the machine is left in a final state
- bool OnBootEvtReceived(SharedFD boot_events_pipe);
- bool BootCompleted() const;
- bool BootFailed() const;
-
- void SendExitCode(RunnerExitCodes exit_code, SharedFD fd);
- bool MaybeWriteNotification();
-
- std::thread boot_event_handler_;
- SharedFD fg_launcher_pipe_;
- SharedFD reboot_notification_;
- int state_;
- static const int kBootStarted = 0;
- static const int kGuestBootCompleted = 1 << 0;
- static const int kGuestBootFailed = 1 << 1;
-};
+fruit::Component<fruit::Required<const CuttlefishConfig, KernelLogPipeProvider>>
+bootStateMachineComponent();
} // namespace cuttlefish
diff --git a/host/commands/run_cvd/launch.cc b/host/commands/run_cvd/launch.cc
index a6184b2..235a3ce 100644
--- a/host/commands/run_cvd/launch.cc
+++ b/host/commands/run_cvd/launch.cc
@@ -16,18 +16,30 @@
#include "host/commands/run_cvd/launch.h"
#include <android-base/logging.h>
+
+#include <unordered_set>
#include <utility>
+#include <vector>
#include "common/libs/fs/shared_fd.h"
#include "common/libs/utils/files.h"
+#include "common/libs/utils/network.h"
+#include "common/libs/utils/result.h"
#include "common/libs/utils/subprocess.h"
#include "host/commands/run_cvd/process_monitor.h"
+#include "host/commands/run_cvd/reporting.h"
#include "host/commands/run_cvd/runner_defs.h"
#include "host/libs/config/cuttlefish_config.h"
+#include "host/libs/config/inject.h"
#include "host/libs/config/known_paths.h"
+#include "host/libs/vm_manager/crosvm_builder.h"
+#include "host/libs/vm_manager/crosvm_manager.h"
+#include "host/libs/vm_manager/vm_manager.h"
namespace cuttlefish {
+using vm_manager::VmManager;
+
namespace {
template <typename T>
@@ -39,335 +51,770 @@
} // namespace
-KernelLogMonitorData LaunchKernelLogMonitor(
- const CuttlefishConfig& config, unsigned int number_of_event_pipes) {
- auto instance = config.ForDefaultInstance();
- auto log_name = instance.kernel_log_pipe_name();
- if (mkfifo(log_name.c_str(), 0600) != 0) {
- LOG(ERROR) << "Unable to create named pipe at " << log_name << ": "
- << strerror(errno);
- return {};
+class KernelLogMonitor : public CommandSource,
+ public KernelLogPipeProvider,
+ public DiagnosticInformation {
+ public:
+ INJECT(KernelLogMonitor(const CuttlefishConfig::InstanceSpecific& instance))
+ : instance_(instance) {}
+
+ // DiagnosticInformation
+ std::vector<std::string> Diagnostics() const override {
+ return {"Kernel log: " + instance_.PerInstancePath("kernel.log")};
}
- SharedFD pipe;
- // Open the pipe here (from the launcher) to ensure the pipe is not deleted
- // due to the usage counters in the kernel reaching zero. If this is not done
- // and the kernel_log_monitor crashes for some reason the VMM may get SIGPIPE.
- pipe = SharedFD::Open(log_name.c_str(), O_RDWR);
- Command command(KernelLogMonitorBinary());
- command.AddParameter("-log_pipe_fd=", pipe);
+ // CommandSource
+ std::vector<Command> Commands() override {
+ Command command(KernelLogMonitorBinary());
+ command.AddParameter("-log_pipe_fd=", fifo_);
- KernelLogMonitorData ret;
-
- if (number_of_event_pipes > 0) {
- command.AddParameter("-subscriber_fds=");
- for (unsigned int i = 0; i < number_of_event_pipes; ++i) {
- SharedFD event_pipe_write_end, event_pipe_read_end;
- if (!SharedFD::Pipe(&event_pipe_read_end, &event_pipe_write_end)) {
- LOG(ERROR) << "Unable to create kernel log events pipe: " << strerror(errno);
- std::exit(RunnerExitCodes::kPipeIOError);
+ if (!event_pipe_write_ends_.empty()) {
+ command.AddParameter("-subscriber_fds=");
+ for (size_t i = 0; i < event_pipe_write_ends_.size(); i++) {
+ if (i > 0) {
+ command.AppendToLastParameter(",");
+ }
+ command.AppendToLastParameter(event_pipe_write_ends_[i]);
}
- if (i > 0) {
- command.AppendToLastParameter(",");
+ }
+
+ return single_element_emplace(std::move(command));
+ }
+
+ // KernelLogPipeProvider
+ SharedFD KernelLogPipe() override {
+ CHECK(!event_pipe_read_ends_.empty()) << "No more kernel pipes left";
+ SharedFD ret = event_pipe_read_ends_.back();
+ event_pipe_read_ends_.pop_back();
+ return ret;
+ }
+
+ private:
+ // SetupFeature
+ bool Enabled() const override { return true; }
+ std::string Name() const override { return "KernelLogMonitor"; }
+
+ private:
+ std::unordered_set<SetupFeature*> Dependencies() const override { return {}; }
+ Result<void> ResultSetup() override {
+ auto log_name = instance_.kernel_log_pipe_name();
+ CF_EXPECT(mkfifo(log_name.c_str(), 0600) == 0,
+ "Unable to create named pipe at " << log_name << ": "
+ << strerror(errno));
+
+ // Open the pipe here (from the launcher) to ensure the pipe is not deleted
+ // due to the usage counters in the kernel reaching zero. If this is not
+ // done and the kernel_log_monitor crashes for some reason the VMM may get
+ // SIGPIPE.
+ fifo_ = SharedFD::Open(log_name, O_RDWR);
+ CF_EXPECT(fifo_->IsOpen(),
+ "Unable to open \"" << log_name << "\": " << fifo_->StrError());
+
+ // TODO(schuffelen): Find a way to calculate this dynamically.
+ int number_of_event_pipes = 4;
+ if (number_of_event_pipes > 0) {
+ for (unsigned int i = 0; i < number_of_event_pipes; ++i) {
+ SharedFD event_pipe_write_end, event_pipe_read_end;
+ CF_EXPECT(SharedFD::Pipe(&event_pipe_read_end, &event_pipe_write_end),
+ "Failed creating kernel log pipe: " << strerror(errno));
+ event_pipe_write_ends_.push_back(event_pipe_write_end);
+ event_pipe_read_ends_.push_back(event_pipe_read_end);
}
- command.AppendToLastParameter(event_pipe_write_end);
- ret.pipes.push_back(event_pipe_read_end);
}
- }
-
- ret.commands.emplace_back(std::move(command));
-
- return ret;
-}
-
-std::vector<Command> LaunchRootCanal(const CuttlefishConfig& config) {
- if (!config.enable_host_bluetooth()) {
return {};
}
- auto instance = config.ForDefaultInstance();
- Command command(RootCanalBinary());
+ const CuttlefishConfig::InstanceSpecific& instance_;
+ SharedFD fifo_;
+ std::vector<SharedFD> event_pipe_write_ends_;
+ std::vector<SharedFD> event_pipe_read_ends_;
+};
- // Test port
- command.AddParameter(instance.rootcanal_test_port());
- // HCI server port
- command.AddParameter(instance.rootcanal_hci_port());
- // Link server port
- command.AddParameter(instance.rootcanal_link_port());
- // Bluetooth controller properties file
- command.AddParameter("--controller_properties_file=",
- instance.rootcanal_config_file());
- // Default commands file
- command.AddParameter("--default_commands_file=",
- instance.rootcanal_default_commands_file());
+class LogTeeCreator {
+ public:
+ INJECT(LogTeeCreator(const CuttlefishConfig::InstanceSpecific& instance))
+ : instance_(instance) {}
- return single_element_emplace(std::move(command));
-}
+ Command CreateLogTee(Command& cmd, const std::string& process_name) {
+ auto name_with_ext = process_name + "_logs.fifo";
+ auto logs_path = instance_.PerInstanceInternalPath(name_with_ext.c_str());
+ auto logs = SharedFD::Fifo(logs_path, 0666);
+ if (!logs->IsOpen()) {
+ LOG(FATAL) << "Failed to create fifo for " << process_name
+ << " output: " << logs->StrError();
+ }
-std::vector<Command> LaunchLogcatReceiver(const CuttlefishConfig& config) {
- auto instance = config.ForDefaultInstance();
- auto log_name = instance.logcat_pipe_name();
- if (mkfifo(log_name.c_str(), 0600) != 0) {
- LOG(ERROR) << "Unable to create named pipe at " << log_name << ": "
- << strerror(errno);
- return {};
+ cmd.RedirectStdIO(Subprocess::StdIOChannel::kStdOut, logs);
+ cmd.RedirectStdIO(Subprocess::StdIOChannel::kStdErr, logs);
+
+ return Command(HostBinaryPath("log_tee"))
+ .AddParameter("--process_name=", process_name)
+ .AddParameter("--log_fd_in=", logs);
}
- SharedFD pipe;
- // Open the pipe here (from the launcher) to ensure the pipe is not deleted
- // due to the usage counters in the kernel reaching zero. If this is not done
- // and the logcat_receiver crashes for some reason the VMM may get SIGPIPE.
- pipe = SharedFD::Open(log_name.c_str(), O_RDWR);
- Command command(LogcatReceiverBinary());
- command.AddParameter("-log_pipe_fd=", pipe);
+ private:
+ const CuttlefishConfig::InstanceSpecific& instance_;
+};
- return single_element_emplace(std::move(command));
-}
+class RootCanal : public CommandSource {
+ public:
+ INJECT(RootCanal(const CuttlefishConfig& config,
+ const CuttlefishConfig::InstanceSpecific& instance,
+ LogTeeCreator& log_tee))
+ : config_(config), instance_(instance), log_tee_(log_tee) {}
-std::vector<Command> LaunchConfigServer(const CuttlefishConfig& config) {
- auto instance = config.ForDefaultInstance();
- auto port = instance.config_server_port();
- auto socket = SharedFD::VsockServer(port, SOCK_STREAM);
- if (!socket->IsOpen()) {
- LOG(ERROR) << "Unable to create configuration server socket: "
- << socket->StrError();
- std::exit(RunnerExitCodes::kConfigServerError);
- }
- Command cmd(ConfigServerBinary());
- cmd.AddParameter("-server_fd=", socket);
- return single_element_emplace(std::move(cmd));
-}
-
-std::vector<Command> LaunchTombstoneReceiver(const CuttlefishConfig& config) {
- auto instance = config.ForDefaultInstance();
-
- std::string tombstoneDir = instance.PerInstancePath("tombstones");
- if (!DirectoryExists(tombstoneDir.c_str())) {
- LOG(DEBUG) << "Setting up " << tombstoneDir;
- if (mkdir(tombstoneDir.c_str(), S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH) <
- 0) {
- LOG(ERROR) << "Failed to create tombstone directory: " << tombstoneDir
- << ". Error: " << errno;
- exit(RunnerExitCodes::kTombstoneDirCreationError);
+ // CommandSource
+ std::vector<Command> Commands() override {
+ if (!Enabled()) {
return {};
}
+ Command command(RootCanalBinary());
+
+ // Test port
+ command.AddParameter(config_.rootcanal_test_port());
+ // HCI server port
+ command.AddParameter(config_.rootcanal_hci_port());
+ // Link server port
+ command.AddParameter(config_.rootcanal_link_port());
+ // Bluetooth controller properties file
+ command.AddParameter("--controller_properties_file=",
+ config_.rootcanal_config_file());
+ // Default commands file
+ command.AddParameter("--default_commands_file=",
+ config_.rootcanal_default_commands_file());
+
+ std::vector<Command> commands;
+ commands.emplace_back(log_tee_.CreateLogTee(command, "rootcanal"));
+ commands.emplace_back(std::move(command));
+ return commands;
}
- auto port = instance.tombstone_receiver_port();
- auto socket = SharedFD::VsockServer(port, SOCK_STREAM);
- if (!socket->IsOpen()) {
- LOG(ERROR) << "Unable to create tombstone server socket: "
- << socket->StrError();
- std::exit(RunnerExitCodes::kTombstoneServerError);
- return {};
+ // SetupFeature
+ std::string Name() const override { return "RootCanal"; }
+ bool Enabled() const override {
+ return config_.enable_host_bluetooth() && instance_.start_rootcanal();
}
- Command cmd(TombstoneReceiverBinary());
- cmd.AddParameter("-server_fd=", socket);
- cmd.AddParameter("-tombstone_dir=", tombstoneDir);
- return single_element_emplace(std::move(cmd));
-}
+ private:
+ std::unordered_set<SetupFeature*> Dependencies() const override { return {}; }
+ bool Setup() override { return true; }
-std::vector<Command> LaunchMetrics() {
- return single_element_emplace(Command(MetricsBinary()));
-}
+ const CuttlefishConfig& config_;
+ const CuttlefishConfig::InstanceSpecific& instance_;
+ LogTeeCreator& log_tee_;
+};
-std::vector<Command> LaunchGnssGrpcProxyServerIfEnabled(
- const CuttlefishConfig& config) {
- if (!config.enable_gnss_grpc_proxy() || !FileExists(GnssGrpcProxyBinary())) {
+class LogcatReceiver : public CommandSource, public DiagnosticInformation {
+ public:
+ INJECT(LogcatReceiver(const CuttlefishConfig::InstanceSpecific& instance))
+ : instance_(instance) {}
+ // DiagnosticInformation
+ std::vector<std::string> Diagnostics() const override {
+ return {"Logcat output: " + instance_.logcat_path()};
+ }
+
+ // CommandSource
+ std::vector<Command> Commands() override {
+ return single_element_emplace(
+ Command(LogcatReceiverBinary()).AddParameter("-log_pipe_fd=", pipe_));
+ }
+
+ // SetupFeature
+ std::string Name() const override { return "LogcatReceiver"; }
+ bool Enabled() const override { return true; }
+
+ private:
+ std::unordered_set<SetupFeature*> Dependencies() const override { return {}; }
+ Result<void> ResultSetup() {
+ auto log_name = instance_.logcat_pipe_name();
+ CF_EXPECT(mkfifo(log_name.c_str(), 0600) == 0,
+ "Unable to create named pipe at " << log_name << ": "
+ << strerror(errno));
+ // Open the pipe here (from the launcher) to ensure the pipe is not deleted
+ // due to the usage counters in the kernel reaching zero. If this is not
+ // done and the logcat_receiver crashes for some reason the VMM may get
+ // SIGPIPE.
+ pipe_ = SharedFD::Open(log_name.c_str(), O_RDWR);
+ CF_EXPECT(pipe_->IsOpen(),
+ "Can't open \"" << log_name << "\": " << pipe_->StrError());
return {};
}
- Command gnss_grpc_proxy_cmd(GnssGrpcProxyBinary());
- auto instance = config.ForDefaultInstance();
+ const CuttlefishConfig::InstanceSpecific& instance_;
+ SharedFD pipe_;
+};
- auto gnss_in_pipe_name = instance.gnss_in_pipe_name();
- if (mkfifo(gnss_in_pipe_name.c_str(), 0600) != 0) {
- auto error = errno;
- LOG(ERROR) << "Failed to create gnss input fifo for crosvm: "
- << strerror(error);
+class ConfigServer : public CommandSource {
+ public:
+ INJECT(ConfigServer(const CuttlefishConfig::InstanceSpecific& instance))
+ : instance_(instance) {}
+
+ // CommandSource
+ std::vector<Command> Commands() override {
+ return single_element_emplace(
+ Command(ConfigServerBinary()).AddParameter("-server_fd=", socket_));
+ }
+
+ // SetupFeature
+ std::string Name() const override { return "ConfigServer"; }
+ bool Enabled() const override { return true; }
+
+ private:
+ std::unordered_set<SetupFeature*> Dependencies() const override { return {}; }
+ Result<void> ResultSetup() override {
+ auto port = instance_.config_server_port();
+ socket_ = SharedFD::VsockServer(port, SOCK_STREAM);
+ CF_EXPECT(socket_->IsOpen(),
+ "Unable to create configuration server socket: "
+ << socket_->StrError());
return {};
}
- auto gnss_out_pipe_name = instance.gnss_out_pipe_name();
- if (mkfifo(gnss_out_pipe_name.c_str(), 0660) != 0) {
- auto error = errno;
- LOG(ERROR) << "Failed to create gnss output fifo for crosvm: "
- << strerror(error);
+ private:
+ const CuttlefishConfig::InstanceSpecific& instance_;
+ SharedFD socket_;
+};
+
+class TombstoneReceiver : public CommandSource {
+ public:
+ INJECT(TombstoneReceiver(const CuttlefishConfig::InstanceSpecific& instance))
+ : instance_(instance) {}
+
+ // CommandSource
+ std::vector<Command> Commands() override {
+ return single_element_emplace(
+ Command(TombstoneReceiverBinary())
+ .AddParameter("-server_fd=", socket_)
+ .AddParameter("-tombstone_dir=", tombstone_dir_));
+ }
+
+ // SetupFeature
+ std::string Name() const override { return "TombstoneReceiver"; }
+ bool Enabled() const override { return true; }
+
+ private:
+ std::unordered_set<SetupFeature*> Dependencies() const override { return {}; }
+ Result<void> ResultSetup() override {
+ tombstone_dir_ = instance_.PerInstancePath("tombstones");
+ if (!DirectoryExists(tombstone_dir_)) {
+ LOG(DEBUG) << "Setting up " << tombstone_dir_;
+ CF_EXPECT(mkdir(tombstone_dir_.c_str(),
+ S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH) == 0,
+ "Failed to create tombstone directory: "
+ << tombstone_dir_ << ". Error: " << strerror(errno));
+ }
+
+ auto port = instance_.tombstone_receiver_port();
+ socket_ = SharedFD::VsockServer(port, SOCK_STREAM);
+ CF_EXPECT(socket_->IsOpen(), "Unable to create tombstone server socket: "
+ << socket_->StrError());
return {};
}
- // These fds will only be read from or written to, but open them with
- // read and write access to keep them open in case the subprocesses exit
- SharedFD gnss_grpc_proxy_in_wr =
- SharedFD::Open(gnss_in_pipe_name.c_str(), O_RDWR);
- if (!gnss_grpc_proxy_in_wr->IsOpen()) {
- LOG(ERROR) << "Failed to open gnss_grpc_proxy input fifo for writes: "
- << gnss_grpc_proxy_in_wr->StrError();
+ const CuttlefishConfig::InstanceSpecific& instance_;
+ SharedFD socket_;
+ std::string tombstone_dir_;
+};
+
+class MetricsService : public CommandSource {
+ public:
+ INJECT(MetricsService(const CuttlefishConfig& config)) : config_(config) {}
+
+ // CommandSource
+ std::vector<Command> Commands() override {
+ return single_element_emplace(Command(MetricsBinary()));
+ }
+
+ // SetupFeature
+ std::string Name() const override { return "MetricsService"; }
+ bool Enabled() const override {
+ return config_.enable_metrics() == CuttlefishConfig::kYes;
+ }
+
+ private:
+ std::unordered_set<SetupFeature*> Dependencies() const override { return {}; }
+ bool Setup() override { return true; }
+
+ private:
+ const CuttlefishConfig& config_;
+};
+
+class GnssGrpcProxyServer : public CommandSource {
+ public:
+ INJECT(
+ GnssGrpcProxyServer(const CuttlefishConfig& config,
+ const CuttlefishConfig::InstanceSpecific& instance))
+ : config_(config), instance_(instance) {}
+
+ // CommandSource
+ std::vector<Command> Commands() override {
+ Command gnss_grpc_proxy_cmd(GnssGrpcProxyBinary());
+ const unsigned gnss_grpc_proxy_server_port =
+ instance_.gnss_grpc_proxy_server_port();
+ gnss_grpc_proxy_cmd.AddParameter("--gnss_in_fd=", gnss_grpc_proxy_in_wr_);
+ gnss_grpc_proxy_cmd.AddParameter("--gnss_out_fd=", gnss_grpc_proxy_out_rd_);
+ gnss_grpc_proxy_cmd.AddParameter("--gnss_grpc_port=",
+ gnss_grpc_proxy_server_port);
+ if (!instance_.gnss_file_path().empty()) {
+ // If path is provided, proxy will start as local mode.
+ gnss_grpc_proxy_cmd.AddParameter("--gnss_file_path=",
+ instance_.gnss_file_path());
+ }
+ return single_element_emplace(std::move(gnss_grpc_proxy_cmd));
+ }
+
+ // SetupFeature
+ std::string Name() const override { return "GnssGrpcProxyServer"; }
+ bool Enabled() const override {
+ return config_.enable_gnss_grpc_proxy() &&
+ FileExists(GnssGrpcProxyBinary());
+ }
+
+ private:
+ std::unordered_set<SetupFeature*> Dependencies() const override { return {}; }
+ Result<void> ResultSetup() override {
+ std::vector<SharedFD> fifos;
+ std::vector<std::string> fifo_paths = {
+ instance_.PerInstanceInternalPath("gnsshvc_fifo_vm.in"),
+ instance_.PerInstanceInternalPath("gnsshvc_fifo_vm.out"),
+ instance_.PerInstanceInternalPath("locationhvc_fifo_vm.in"),
+ instance_.PerInstanceInternalPath("locationhvc_fifo_vm.out"),
+ };
+ for (const auto& path : fifo_paths) {
+ unlink(path.c_str());
+ CF_EXPECT(mkfifo(path.c_str(), 0660) == 0, "Could not create " << path);
+ auto fd = SharedFD::Open(path, O_RDWR);
+ CF_EXPECT(fd->IsOpen(),
+ "Could not open " << path << ": " << fd->StrError());
+ fifos.push_back(fd);
+ }
+
+ gnss_grpc_proxy_in_wr_ = fifos[0];
+ gnss_grpc_proxy_out_rd_ = fifos[1];
return {};
}
- SharedFD gnss_grpc_proxy_out_rd =
- SharedFD::Open(gnss_out_pipe_name.c_str(), O_RDWR);
- if (!gnss_grpc_proxy_out_rd->IsOpen()) {
- LOG(ERROR) << "Failed to open gnss_grpc_proxy output fifo for reads: "
- << gnss_grpc_proxy_out_rd->StrError();
+ private:
+ const CuttlefishConfig& config_;
+ const CuttlefishConfig::InstanceSpecific& instance_;
+ SharedFD gnss_grpc_proxy_in_wr_;
+ SharedFD gnss_grpc_proxy_out_rd_;
+};
+
+class BluetoothConnector : public CommandSource {
+ public:
+ INJECT(BluetoothConnector(const CuttlefishConfig& config,
+ const CuttlefishConfig::InstanceSpecific& instance))
+ : config_(config), instance_(instance) {}
+
+ // CommandSource
+ std::vector<Command> Commands() override {
+ Command command(HostBinaryPath("bt_connector"));
+ command.AddParameter("-bt_out=", fifos_[0]);
+ command.AddParameter("-bt_in=", fifos_[1]);
+ command.AddParameter("-hci_port=", config_.rootcanal_hci_port());
+ command.AddParameter("-link_port=", config_.rootcanal_link_port());
+ command.AddParameter("-test_port=", config_.rootcanal_test_port());
+ return single_element_emplace(std::move(command));
+ }
+
+ // SetupFeature
+ std::string Name() const override { return "BluetoothConnector"; }
+ bool Enabled() const override { return config_.enable_host_bluetooth(); }
+
+ private:
+ std::unordered_set<SetupFeature*> Dependencies() const override { return {}; }
+ Result<void> ResultSetup() {
+ std::vector<std::string> fifo_paths = {
+ instance_.PerInstanceInternalPath("bt_fifo_vm.in"),
+ instance_.PerInstanceInternalPath("bt_fifo_vm.out"),
+ };
+ for (const auto& path : fifo_paths) {
+ unlink(path.c_str());
+ CF_EXPECT(mkfifo(path.c_str(), 0660) == 0, "Could not create " << path);
+ auto fd = SharedFD::Open(path, O_RDWR);
+ CF_EXPECT(fd->IsOpen(),
+ "Could not open " << path << ": " << fd->StrError());
+ fifos_.push_back(fd);
+ }
return {};
}
- const unsigned gnss_grpc_proxy_server_port =
- instance.gnss_grpc_proxy_server_port();
- gnss_grpc_proxy_cmd.AddParameter("--gnss_in_fd=", gnss_grpc_proxy_in_wr);
- gnss_grpc_proxy_cmd.AddParameter("--gnss_out_fd=", gnss_grpc_proxy_out_rd);
- gnss_grpc_proxy_cmd.AddParameter("--gnss_grpc_port=",
- gnss_grpc_proxy_server_port);
- if (!instance.gnss_file_path().empty()) {
- // If path is provided, proxy will start as local mode.
- gnss_grpc_proxy_cmd.AddParameter("--gnss_file_path=",
- instance.gnss_file_path());
- }
- return single_element_emplace(std::move(gnss_grpc_proxy_cmd));
-}
+ private:
+ const CuttlefishConfig& config_;
+ const CuttlefishConfig::InstanceSpecific& instance_;
+ std::vector<SharedFD> fifos_;
+};
-std::vector<Command> LaunchBluetoothConnector(const CuttlefishConfig& config) {
- auto instance = config.ForDefaultInstance();
- std::vector<std::string> fifo_paths = {
- instance.PerInstanceInternalPath("bt_fifo_vm.in"),
- instance.PerInstanceInternalPath("bt_fifo_vm.out"),
- };
- std::vector<SharedFD> fifos;
- for (const auto& path : fifo_paths) {
- unlink(path.c_str());
- if (mkfifo(path.c_str(), 0660) < 0) {
- PLOG(ERROR) << "Could not create " << path;
+class SecureEnvironment : public CommandSource {
+ public:
+ INJECT(SecureEnvironment(const CuttlefishConfig& config,
+ const CuttlefishConfig::InstanceSpecific& instance,
+ KernelLogPipeProvider& kernel_log_pipe_provider))
+ : config_(config),
+ instance_(instance),
+ kernel_log_pipe_provider_(kernel_log_pipe_provider) {}
+
+ // CommandSource
+ std::vector<Command> Commands() override {
+ Command command(HostBinaryPath("secure_env"));
+ command.AddParameter("-confui_server_fd=", confui_server_fd_);
+ command.AddParameter("-keymaster_fd_out=", fifos_[0]);
+ command.AddParameter("-keymaster_fd_in=", fifos_[1]);
+ command.AddParameter("-gatekeeper_fd_out=", fifos_[2]);
+ command.AddParameter("-gatekeeper_fd_in=", fifos_[3]);
+
+ const auto& secure_hals = config_.secure_hals();
+ bool secure_keymint = secure_hals.count(SecureHal::Keymint) > 0;
+ command.AddParameter("-keymint_impl=", secure_keymint ? "tpm" : "software");
+ bool secure_gatekeeper = secure_hals.count(SecureHal::Gatekeeper) > 0;
+ auto gatekeeper_impl = secure_gatekeeper ? "tpm" : "software";
+ command.AddParameter("-gatekeeper_impl=", gatekeeper_impl);
+
+ command.AddParameter("-kernel_events_fd=", kernel_log_pipe_);
+
+ return single_element_emplace(std::move(command));
+ }
+
+ // SetupFeature
+ std::string Name() const override { return "SecureEnvironment"; }
+ bool Enabled() const override { return true; }
+
+ private:
+ std::unordered_set<SetupFeature*> Dependencies() const override {
+ return {&kernel_log_pipe_provider_};
+ }
+ Result<void> ResultSetup() override {
+ std::vector<std::string> fifo_paths = {
+ instance_.PerInstanceInternalPath("keymaster_fifo_vm.in"),
+ instance_.PerInstanceInternalPath("keymaster_fifo_vm.out"),
+ instance_.PerInstanceInternalPath("gatekeeper_fifo_vm.in"),
+ instance_.PerInstanceInternalPath("gatekeeper_fifo_vm.out"),
+ };
+ std::vector<SharedFD> fifos;
+ for (const auto& path : fifo_paths) {
+ unlink(path.c_str());
+ CF_EXPECT(mkfifo(path.c_str(), 0660) == 0, "Could not create " << path);
+ auto fd = SharedFD::Open(path, O_RDWR);
+ CF_EXPECT(fd->IsOpen(),
+ "Could not open " << path << ": " << fd->StrError());
+ fifos_.push_back(fd);
+ }
+
+ auto confui_socket_path =
+ instance_.PerInstanceInternalPath("confui_sign.sock");
+ confui_server_fd_ = SharedFD::SocketLocalServer(confui_socket_path, false,
+ SOCK_STREAM, 0600);
+ CF_EXPECT(confui_server_fd_->IsOpen(),
+ "Could not open " << confui_socket_path << ": "
+ << confui_server_fd_->StrError());
+ kernel_log_pipe_ = kernel_log_pipe_provider_.KernelLogPipe();
+
+ return {};
+ }
+
+ const CuttlefishConfig& config_;
+ const CuttlefishConfig::InstanceSpecific& instance_;
+ SharedFD confui_server_fd_;
+ std::vector<SharedFD> fifos_;
+ KernelLogPipeProvider& kernel_log_pipe_provider_;
+ SharedFD kernel_log_pipe_;
+};
+
+class VehicleHalServer : public CommandSource {
+ public:
+ INJECT(VehicleHalServer(const CuttlefishConfig& config,
+ const CuttlefishConfig::InstanceSpecific& instance))
+ : config_(config), instance_(instance) {}
+
+ // CommandSource
+ std::vector<Command> Commands() override {
+ Command grpc_server(VehicleHalGrpcServerBinary());
+
+ const unsigned vhal_server_cid = 2;
+ const unsigned vhal_server_port = instance_.vehicle_hal_server_port();
+ const std::string vhal_server_power_state_file =
+ AbsolutePath(instance_.PerInstancePath("power_state"));
+ const std::string vhal_server_power_state_socket =
+ AbsolutePath(instance_.PerInstancePath("power_state_socket"));
+
+ grpc_server.AddParameter("--server_cid=", vhal_server_cid);
+ grpc_server.AddParameter("--server_port=", vhal_server_port);
+ grpc_server.AddParameter("--power_state_file=",
+ vhal_server_power_state_file);
+ grpc_server.AddParameter("--power_state_socket=",
+ vhal_server_power_state_socket);
+ return single_element_emplace(std::move(grpc_server));
+ }
+
+ // SetupFeature
+ std::string Name() const override { return "VehicleHalServer"; }
+ bool Enabled() const override {
+ return config_.enable_vehicle_hal_grpc_server() &&
+ FileExists(VehicleHalGrpcServerBinary());
+ }
+
+ private:
+ std::unordered_set<SetupFeature*> Dependencies() const override { return {}; }
+ bool Setup() override { return true; }
+
+ private:
+ const CuttlefishConfig& config_;
+ const CuttlefishConfig::InstanceSpecific& instance_;
+};
+
+class ConsoleForwarder : public CommandSource, public DiagnosticInformation {
+ public:
+ INJECT(ConsoleForwarder(const CuttlefishConfig& config,
+ const CuttlefishConfig::InstanceSpecific& instance))
+ : config_(config), instance_(instance) {}
+ // DiagnosticInformation
+ std::vector<std::string> Diagnostics() const override {
+ if (Enabled()) {
+ return {"To access the console run: screen " + instance_.console_path()};
+ } else {
+ return {"Serial console is disabled; use -console=true to enable it."};
+ }
+ }
+
+ // CommandSource
+ std::vector<Command> Commands() override {
+ Command console_forwarder_cmd(ConsoleForwarderBinary());
+
+ console_forwarder_cmd.AddParameter("--console_in_fd=",
+ console_forwarder_in_wr_);
+ console_forwarder_cmd.AddParameter("--console_out_fd=",
+ console_forwarder_out_rd_);
+ return single_element_emplace(std::move(console_forwarder_cmd));
+ }
+
+ // SetupFeature
+ std::string Name() const override { return "ConsoleForwarder"; }
+ bool Enabled() const override { return config_.console(); }
+
+ private:
+ std::unordered_set<SetupFeature*> Dependencies() const override { return {}; }
+ Result<void> ResultSetup() override {
+ auto console_in_pipe_name = instance_.console_in_pipe_name();
+ CF_EXPECT(
+ mkfifo(console_in_pipe_name.c_str(), 0600) == 0,
+ "Failed to create console input fifo for crosvm: " << strerror(errno));
+
+ auto console_out_pipe_name = instance_.console_out_pipe_name();
+ CF_EXPECT(
+ mkfifo(console_out_pipe_name.c_str(), 0660) == 0,
+ "Failed to create console output fifo for crosvm: " << strerror(errno));
+
+ // These fds will only be read from or written to, but open them with
+ // read and write access to keep them open in case the subprocesses exit
+ console_forwarder_in_wr_ =
+ SharedFD::Open(console_in_pipe_name.c_str(), O_RDWR);
+ CF_EXPECT(console_forwarder_in_wr_->IsOpen(),
+ "Failed to open console_forwarder input fifo for writes: "
+ << console_forwarder_in_wr_->StrError());
+
+ console_forwarder_out_rd_ =
+ SharedFD::Open(console_out_pipe_name.c_str(), O_RDWR);
+ CF_EXPECT(console_forwarder_out_rd_->IsOpen(),
+ "Failed to open console_forwarder output fifo for reads: "
+ << console_forwarder_out_rd_->StrError());
+ return {};
+ }
+
+ const CuttlefishConfig& config_;
+ const CuttlefishConfig::InstanceSpecific& instance_;
+ SharedFD console_forwarder_in_wr_;
+ SharedFD console_forwarder_out_rd_;
+};
+
+class WmediumdServer : public CommandSource {
+ public:
+ INJECT(WmediumdServer(const CuttlefishConfig& config,
+ const CuttlefishConfig::InstanceSpecific& instance,
+ LogTeeCreator& log_tee))
+ : config_(config), instance_(instance), log_tee_(log_tee) {}
+
+ // CommandSource
+ std::vector<Command> Commands() override {
+ Command cmd(WmediumdBinary());
+ cmd.AddParameter("-u", config_.vhost_user_mac80211_hwsim());
+ cmd.AddParameter("-a", config_.wmediumd_api_server_socket());
+ cmd.AddParameter("-c", config_path_);
+
+ std::vector<Command> commands;
+ commands.emplace_back(log_tee_.CreateLogTee(cmd, "wmediumd"));
+ commands.emplace_back(std::move(cmd));
+ return commands;
+ }
+
+ // SetupFeature
+ std::string Name() const override { return "WmediumdServer"; }
+ bool Enabled() const override {
+#ifndef ENFORCE_MAC80211_HWSIM
+ return false;
+#else
+ return instance_.start_wmediumd();
+#endif
+ }
+
+ private:
+ std::unordered_set<SetupFeature*> Dependencies() const override { return {}; }
+ Result<void> ResultSetup() override {
+ // If wmediumd configuration is given, use it
+ if (!config_.wmediumd_config().empty()) {
+ config_path_ = config_.wmediumd_config();
return {};
}
- auto fd = SharedFD::Open(path, O_RDWR);
- if (!fd->IsOpen()) {
- LOG(ERROR) << "Could not open " << path << ": " << fd->StrError();
- return {};
+ // Otherwise, generate wmediumd configuration using the current wifi mac
+ // prefix before start
+ config_path_ = instance_.PerInstanceInternalPath("wmediumd.cfg");
+ Command gen_config_cmd(WmediumdGenConfigBinary());
+ gen_config_cmd.AddParameter("-o", config_path_);
+ gen_config_cmd.AddParameter("-p", instance_.wifi_mac_prefix());
+
+ int success = gen_config_cmd.Start().Wait();
+ CF_EXPECT(success == 0, "Unable to run " << gen_config_cmd.Executable()
+ << ". Exited with status "
+ << success);
+ return {};
+ }
+
+ const CuttlefishConfig& config_;
+ const CuttlefishConfig::InstanceSpecific& instance_;
+ LogTeeCreator& log_tee_;
+ std::string config_path_;
+};
+
+class VmmCommands : public CommandSource {
+ public:
+ INJECT(VmmCommands(const CuttlefishConfig& config, VmManager& vmm))
+ : config_(config), vmm_(vmm) {}
+
+ // CommandSource
+ std::vector<Command> Commands() override {
+ return vmm_.StartCommands(config_);
+ }
+
+ // SetupFeature
+ std::string Name() const override { return "VirtualMachineManager"; }
+ bool Enabled() const override { return true; }
+
+ private:
+ std::unordered_set<SetupFeature*> Dependencies() const override { return {}; }
+ bool Setup() override { return true; }
+
+ const CuttlefishConfig& config_;
+ VmManager& vmm_;
+};
+
+class OpenWrt : public CommandSource {
+ public:
+ INJECT(OpenWrt(const CuttlefishConfig& config,
+ const CuttlefishConfig::InstanceSpecific& instance,
+ LogTeeCreator& log_tee))
+ : config_(config), instance_(instance), log_tee_(log_tee) {}
+
+ // CommandSource
+ std::vector<Command> Commands() override {
+ constexpr auto crosvm_for_ap_socket = "ap_control.sock";
+
+ CrosvmBuilder ap_cmd;
+ ap_cmd.SetBinary(config_.crosvm_binary());
+ ap_cmd.AddControlSocket(
+ instance_.PerInstanceInternalPath(crosvm_for_ap_socket));
+
+ if (!config_.vhost_user_mac80211_hwsim().empty()) {
+ ap_cmd.Cmd().AddParameter("--vhost-user-mac80211-hwsim=",
+ config_.vhost_user_mac80211_hwsim());
}
- fifos.push_back(fd);
- }
-
- Command command(DefaultHostArtifactsPath("bin/bt_connector"));
- command.AddParameter("-bt_out=", fifos[0]);
- command.AddParameter("-bt_in=", fifos[1]);
- command.AddParameter("-hci_port=", instance.rootcanal_hci_port());
- command.AddParameter("-link_port=", instance.rootcanal_link_port());
- command.AddParameter("-test_port=", instance.rootcanal_test_port());
- return single_element_emplace(std::move(command));
-}
-
-std::vector<Command> LaunchSecureEnvironment(const CuttlefishConfig& config) {
- auto instance = config.ForDefaultInstance();
- std::vector<std::string> fifo_paths = {
- instance.PerInstanceInternalPath("keymaster_fifo_vm.in"),
- instance.PerInstanceInternalPath("keymaster_fifo_vm.out"),
- instance.PerInstanceInternalPath("gatekeeper_fifo_vm.in"),
- instance.PerInstanceInternalPath("gatekeeper_fifo_vm.out"),
- };
- std::vector<SharedFD> fifos;
- for (const auto& path : fifo_paths) {
- unlink(path.c_str());
- if (mkfifo(path.c_str(), 0600) < 0) {
- PLOG(ERROR) << "Could not create " << path;
- return {};
+ SharedFD wifi_tap = ap_cmd.AddTap(instance_.wifi_tap_name());
+ // Only run the leases workaround if we are not using the new network
+ // bridge architecture - in that case, we have a wider DHCP address
+ // space and stale leases should be much less of an issue
+ if (!FileExists("/var/run/cuttlefish-dnsmasq-cvd-wbr.leases") &&
+ wifi_tap->IsOpen()) {
+ // TODO(schuffelen): QEMU also needs this and this is not the best place
+ // for this code. Find a better place to put it.
+ auto lease_file =
+ ForCurrentInstance("/var/run/cuttlefish-dnsmasq-cvd-wbr-") +
+ ".leases";
+ std::uint8_t dhcp_server_ip[] = {
+ 192, 168, 96, (std::uint8_t)(ForCurrentInstance(1) * 4 - 3)};
+ if (!ReleaseDhcpLeases(lease_file, wifi_tap, dhcp_server_ip)) {
+ LOG(ERROR)
+ << "Failed to release wifi DHCP leases. Connecting to the wifi "
+ << "network may not work.";
+ }
}
- auto fd = SharedFD::Open(path, O_RDWR);
- if (!fd->IsOpen()) {
- LOG(ERROR) << "Could not open " << path << ": " << fd->StrError();
- return {};
+ if (config_.enable_sandbox()) {
+ ap_cmd.Cmd().AddParameter("--seccomp-policy-dir=",
+ config_.seccomp_policy_dir());
+ } else {
+ ap_cmd.Cmd().AddParameter("--disable-sandbox");
}
- fifos.push_back(fd);
+ ap_cmd.Cmd().AddParameter("--rwdisk=",
+ instance_.PerInstancePath("ap_overlay.img"));
+ ap_cmd.Cmd().AddParameter(
+ "--disk=", instance_.PerInstancePath("persistent_composite.img"));
+ ap_cmd.Cmd().AddParameter("--params=\"root=" + config_.ap_image_dev_path() +
+ "\"");
+
+ auto kernel_logs_path = instance_.PerInstanceLogPath("crosvm_openwrt.log");
+ ap_cmd.AddSerialConsoleReadOnly(kernel_logs_path);
+
+ ap_cmd.Cmd().AddParameter(config_.ap_kernel_image());
+
+ std::vector<Command> commands;
+ commands.emplace_back(log_tee_.CreateLogTee(ap_cmd.Cmd(), "openwrt"));
+ commands.emplace_back(std::move(ap_cmd.Cmd()));
+ return commands;
}
- Command command(HostBinaryPath("secure_env"));
- command.AddParameter("-keymaster_fd_out=", fifos[0]);
- command.AddParameter("-keymaster_fd_in=", fifos[1]);
- command.AddParameter("-gatekeeper_fd_out=", fifos[2]);
- command.AddParameter("-gatekeeper_fd_in=", fifos[3]);
+ // SetupFeature
+ std::string Name() const override { return "OpenWrt"; }
+ bool Enabled() const override {
+#ifndef ENFORCE_MAC80211_HWSIM
+ return false;
+#else
+ return instance_.start_ap() &&
+ config_.vm_manager() == vm_manager::CrosvmManager::name();
+#endif
+ }
- const auto& secure_hals = config.secure_hals();
- bool secure_keymint = secure_hals.count(SecureHal::Keymint) > 0;
- command.AddParameter("-keymint_impl=", secure_keymint ? "tpm" : "software");
- bool secure_gatekeeper = secure_hals.count(SecureHal::Gatekeeper) > 0;
- auto gatekeeper_impl = secure_gatekeeper ? "tpm" : "software";
- command.AddParameter("-gatekeeper_impl=", gatekeeper_impl);
+ private:
+ std::unordered_set<SetupFeature*> Dependencies() const override { return {}; }
+ bool Setup() override { return true; }
- return single_element_emplace(std::move(command));
+ const CuttlefishConfig& config_;
+ const CuttlefishConfig::InstanceSpecific& instance_;
+ LogTeeCreator& log_tee_;
+};
+
+using PublicDeps = fruit::Required<const CuttlefishConfig, VmManager,
+ const CuttlefishConfig::InstanceSpecific>;
+fruit::Component<PublicDeps, KernelLogPipeProvider> launchComponent() {
+ using InternalDeps = fruit::Required<const CuttlefishConfig, VmManager,
+ const CuttlefishConfig::InstanceSpecific,
+ KernelLogPipeProvider>;
+ using Multi = Multibindings<InternalDeps>;
+ using Bases =
+ Multi::Bases<CommandSource, DiagnosticInformation, SetupFeature>;
+ return fruit::createComponent()
+ .bind<KernelLogPipeProvider, KernelLogMonitor>()
+ .install(Bases::Impls<BluetoothConnector>)
+ .install(Bases::Impls<ConfigServer>)
+ .install(Bases::Impls<ConsoleForwarder>)
+ .install(Bases::Impls<GnssGrpcProxyServer>)
+ .install(Bases::Impls<KernelLogMonitor>)
+ .install(Bases::Impls<LogcatReceiver>)
+ .install(Bases::Impls<MetricsService>)
+ .install(Bases::Impls<RootCanal>)
+ .install(Bases::Impls<SecureEnvironment>)
+ .install(Bases::Impls<TombstoneReceiver>)
+ .install(Bases::Impls<VehicleHalServer>)
+ .install(Bases::Impls<VmmCommands>)
+ .install(Bases::Impls<WmediumdServer>)
+ .install(Bases::Impls<OpenWrt>);
}
-std::vector<Command> LaunchVehicleHalServerIfEnabled(
- const CuttlefishConfig& config) {
- if (!config.enable_vehicle_hal_grpc_server() ||
- !FileExists(config.vehicle_hal_grpc_server_binary())) {
- return {};
- }
-
- Command grpc_server(config.vehicle_hal_grpc_server_binary());
- auto instance = config.ForDefaultInstance();
-
- const unsigned vhal_server_cid = 2;
- const unsigned vhal_server_port = instance.vehicle_hal_server_port();
- const std::string vhal_server_power_state_file =
- AbsolutePath(instance.PerInstancePath("power_state"));
- const std::string vhal_server_power_state_socket =
- AbsolutePath(instance.PerInstancePath("power_state_socket"));
-
- grpc_server.AddParameter("--server_cid=", vhal_server_cid);
- grpc_server.AddParameter("--server_port=", vhal_server_port);
- grpc_server.AddParameter("--power_state_file=", vhal_server_power_state_file);
- grpc_server.AddParameter("--power_state_socket=", vhal_server_power_state_socket);
- return single_element_emplace(std::move(grpc_server));
-}
-
-std::vector<Command> LaunchConsoleForwarderIfEnabled(
- const CuttlefishConfig& config) {
- if (!config.console()) {
- return {};
- }
-
- Command console_forwarder_cmd(ConsoleForwarderBinary());
- auto instance = config.ForDefaultInstance();
-
- auto console_in_pipe_name = instance.console_in_pipe_name();
- if (mkfifo(console_in_pipe_name.c_str(), 0600) != 0) {
- auto error = errno;
- LOG(ERROR) << "Failed to create console input fifo for crosvm: "
- << strerror(error);
- return {};
- }
-
- auto console_out_pipe_name = instance.console_out_pipe_name();
- if (mkfifo(console_out_pipe_name.c_str(), 0660) != 0) {
- auto error = errno;
- LOG(ERROR) << "Failed to create console output fifo for crosvm: "
- << strerror(error);
- return {};
- }
-
- // These fds will only be read from or written to, but open them with
- // read and write access to keep them open in case the subprocesses exit
- SharedFD console_forwarder_in_wr =
- SharedFD::Open(console_in_pipe_name.c_str(), O_RDWR);
- if (!console_forwarder_in_wr->IsOpen()) {
- LOG(ERROR) << "Failed to open console_forwarder input fifo for writes: "
- << console_forwarder_in_wr->StrError();
- return {};
- }
-
- SharedFD console_forwarder_out_rd =
- SharedFD::Open(console_out_pipe_name.c_str(), O_RDWR);
- if (!console_forwarder_out_rd->IsOpen()) {
- LOG(ERROR) << "Failed to open console_forwarder output fifo for reads: "
- << console_forwarder_out_rd->StrError();
- return {};
- }
-
- console_forwarder_cmd.AddParameter("--console_in_fd=", console_forwarder_in_wr);
- console_forwarder_cmd.AddParameter("--console_out_fd=", console_forwarder_out_rd);
- return single_element_emplace(std::move(console_forwarder_cmd));
-}
-
-} // namespace cuttlefish
+} // namespace cuttlefish
diff --git a/host/commands/run_cvd/launch.h b/host/commands/run_cvd/launch.h
index 30d5aff..ead43bc 100644
--- a/host/commands/run_cvd/launch.h
+++ b/host/commands/run_cvd/launch.h
@@ -15,51 +15,34 @@
#pragma once
+#include <fruit/fruit.h>
+
#include <string>
#include <vector>
#include "common/libs/fs/shared_fd.h"
#include "common/libs/utils/subprocess.h"
+#include "host/libs/config/command_source.h"
+#include "host/libs/config/custom_actions.h"
#include "host/libs/config/cuttlefish_config.h"
+#include "host/libs/config/feature.h"
+#include "host/libs/config/kernel_log_pipe_provider.h"
+#include "host/libs/vm_manager/vm_manager.h"
namespace cuttlefish {
-struct KernelLogMonitorData {
- std::vector<SharedFD> pipes;
- std::vector<Command> commands;
-};
+fruit::Component<fruit::Required<const CuttlefishConfig, vm_manager::VmManager,
+ const CuttlefishConfig::InstanceSpecific>,
+ KernelLogPipeProvider>
+launchComponent();
-KernelLogMonitorData LaunchKernelLogMonitor(const CuttlefishConfig& config,
- unsigned int number_of_event_pipes);
-std::vector<Command> LaunchAdbConnectorIfEnabled(
- const CuttlefishConfig& config);
-std::vector<Command> LaunchSocketVsockProxyIfEnabled(
- const CuttlefishConfig& config, SharedFD adbd_events_pipe);
-std::vector<Command> LaunchModemSimulatorIfEnabled(
- const CuttlefishConfig& config);
+fruit::Component<fruit::Required<const CuttlefishConfig,
+ const CuttlefishConfig::InstanceSpecific>>
+launchModemComponent();
-std::vector<Command> LaunchVNCServer(const CuttlefishConfig& config);
-
-std::vector<Command> LaunchTombstoneReceiver(const CuttlefishConfig& config);
-std::vector<Command> LaunchRootCanal(const CuttlefishConfig& config);
-std::vector<Command> LaunchLogcatReceiver(const CuttlefishConfig& config);
-std::vector<Command> LaunchConfigServer(const CuttlefishConfig& config);
-
-std::vector<Command> LaunchWebRTC(const CuttlefishConfig& config,
- SharedFD kernel_log_events_pipe);
-
-std::vector<Command> LaunchMetrics();
-
-std::vector<Command> LaunchGnssGrpcProxyServerIfEnabled(
- const CuttlefishConfig& config);
-
-std::vector<Command> LaunchSecureEnvironment(const CuttlefishConfig& config);
-
-std::vector<Command> LaunchBluetoothConnector(const CuttlefishConfig& config);
-std::vector<Command> LaunchVehicleHalServerIfEnabled(
- const CuttlefishConfig& config);
-
-std::vector<Command> LaunchConsoleForwarderIfEnabled(
- const CuttlefishConfig& config);
+fruit::Component<fruit::Required<const CuttlefishConfig, KernelLogPipeProvider,
+ const CuttlefishConfig::InstanceSpecific,
+ const CustomActionConfigProvider>>
+launchStreamerComponent();
} // namespace cuttlefish
diff --git a/host/commands/run_cvd/launch_adb.cpp b/host/commands/run_cvd/launch_adb.cpp
deleted file mode 100644
index b7996ee..0000000
--- a/host/commands/run_cvd/launch_adb.cpp
+++ /dev/null
@@ -1,156 +0,0 @@
-//
-// Copyright (C) 2019 The Android Open Source Project
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include "host/commands/run_cvd/launch.h"
-
-#include <android-base/logging.h>
-#include <set>
-#include <string>
-#include <utility>
-
-#include "common/libs/fs/shared_fd.h"
-#include "common/libs/utils/subprocess.h"
-#include "host/libs/config/cuttlefish_config.h"
-#include "host/libs/config/known_paths.h"
-
-namespace cuttlefish {
-
-namespace {
-
-std::string GetAdbConnectorTcpArg(const CuttlefishConfig& config) {
- auto instance = config.ForDefaultInstance();
- return std::string{"0.0.0.0:"} + std::to_string(instance.host_port());
-}
-
-std::string GetAdbConnectorVsockArg(const CuttlefishConfig& config) {
- auto instance = config.ForDefaultInstance();
- return std::string{"vsock:"} + std::to_string(instance.vsock_guest_cid()) +
- std::string{":5555"};
-}
-
-bool AdbModeEnabled(const CuttlefishConfig& config, AdbMode mode) {
- return config.adb_mode().count(mode) > 0;
-}
-
-bool AdbVsockTunnelEnabled(const CuttlefishConfig& config) {
- auto instance = config.ForDefaultInstance();
- return instance.vsock_guest_cid() > 2 &&
- AdbModeEnabled(config, AdbMode::VsockTunnel);
-}
-
-bool AdbVsockHalfTunnelEnabled(const CuttlefishConfig& config) {
- auto instance = config.ForDefaultInstance();
- return instance.vsock_guest_cid() > 2 &&
- AdbModeEnabled(config, AdbMode::VsockHalfTunnel);
-}
-
-bool AdbTcpConnectorEnabled(const CuttlefishConfig& config) {
- bool vsock_tunnel = AdbVsockTunnelEnabled(config);
- bool vsock_half_tunnel = AdbVsockHalfTunnelEnabled(config);
- return config.run_adb_connector() && (vsock_tunnel || vsock_half_tunnel);
-}
-
-bool AdbVsockConnectorEnabled(const CuttlefishConfig& config) {
- return config.run_adb_connector() &&
- AdbModeEnabled(config, AdbMode::NativeVsock);
-}
-
-} // namespace
-
-std::vector<Command> LaunchAdbConnectorIfEnabled(
- const CuttlefishConfig& config) {
- Command adb_connector(AdbConnectorBinary());
- std::set<std::string> addresses;
-
- if (AdbTcpConnectorEnabled(config)) {
- addresses.insert(GetAdbConnectorTcpArg(config));
- }
- if (AdbVsockConnectorEnabled(config)) {
- addresses.insert(GetAdbConnectorVsockArg(config));
- }
-
- if (addresses.size() == 0) {
- return {};
- }
- std::string address_arg = "--addresses=";
- for (auto& arg : addresses) {
- address_arg += arg + ",";
- }
- address_arg.pop_back();
- adb_connector.AddParameter(address_arg);
- std::vector<Command> commands;
- commands.emplace_back(std::move(adb_connector));
- return std::move(commands);
-}
-
-std::vector<Command> LaunchSocketVsockProxyIfEnabled(
- const CuttlefishConfig& config, SharedFD adbd_events_pipe) {
- auto instance = config.ForDefaultInstance();
- auto append = [](const std::string& s, const int i) -> std::string {
- return s + std::to_string(i);
- };
- auto tcp_server =
- SharedFD::SocketLocalServer(instance.host_port(), SOCK_STREAM);
- CHECK(tcp_server->IsOpen())
- << "Unable to create socket_vsock_proxy server socket: "
- << tcp_server->StrError();
- std::vector<Command> commands;
- if (AdbVsockTunnelEnabled(config)) {
- Command adb_tunnel(SocketVsockProxyBinary());
- adb_tunnel.AddParameter("-adbd_events_fd=", adbd_events_pipe);
- /**
- * This socket_vsock_proxy (a.k.a. sv proxy) runs on the host. It assumes
- * that another sv proxy runs inside the guest. see:
- * shared/config/init.vendor.rc The sv proxy in the guest exposes
- * vsock:cid:6520 across the cuttlefish instances in multi-tenancy. cid is
- * different per instance.
- *
- * This host sv proxy should cooperate with the guest sv proxy. Thus, one
- * end of the tunnel is vsock:cid:6520 regardless of instance number.
- * Another end faces the host adb daemon via tcp. Thus, the server type is
- * tcp here. The tcp port differs from instance to instance, and is
- * instance.host_port()
- *
- */
- adb_tunnel.AddParameter("--server=tcp");
- adb_tunnel.AddParameter("--vsock_port=6520");
- adb_tunnel.AddParameter(std::string{"--server_fd="}, tcp_server);
- adb_tunnel.AddParameter(std::string{"--vsock_cid="} +
- std::to_string(instance.vsock_guest_cid()));
- commands.emplace_back(std::move(adb_tunnel));
- }
- if (AdbVsockHalfTunnelEnabled(config)) {
- Command adb_tunnel(SocketVsockProxyBinary());
- adb_tunnel.AddParameter("-adbd_events_fd=", adbd_events_pipe);
- /*
- * This socket_vsock_proxy (a.k.a. sv proxy) runs on the host, and
- * cooperates with the adbd inside the guest. See this file:
- * shared/device.mk, especially the line says "persist.adb.tcp.port="
- *
- * The guest adbd is listening on vsock:cid:5555 across cuttlefish
- * instances. Sv proxy faces the host adb daemon via tcp. The server type
- * should be therefore tcp, and the port should differ from instance to
- * instance and be equal to instance.host_port()
- */
- adb_tunnel.AddParameter("--server=tcp");
- adb_tunnel.AddParameter(append("--vsock_port=", 5555));
- adb_tunnel.AddParameter(std::string{"--server_fd="}, tcp_server);
- adb_tunnel.AddParameter(append("--vsock_cid=", instance.vsock_guest_cid()));
- commands.emplace_back(std::move(adb_tunnel));
- }
- return commands;
-}
-
-} // namespace cuttlefish
diff --git a/host/commands/run_cvd/launch_modem.cpp b/host/commands/run_cvd/launch_modem.cpp
index 07af9f1..8648b89 100644
--- a/host/commands/run_cvd/launch_modem.cpp
+++ b/host/commands/run_cvd/launch_modem.cpp
@@ -19,25 +19,21 @@
#include <string.h>
#include <sstream>
#include <string>
+#include <unordered_set>
#include <utility>
#include "common/libs/fs/shared_fd.h"
+#include "common/libs/utils/result.h"
#include "common/libs/utils/subprocess.h"
#include "host/libs/config/cuttlefish_config.h"
#include "host/libs/config/known_paths.h"
namespace cuttlefish {
-static bool StopModemSimulator() {
- auto config = CuttlefishConfig::Get();
- auto instance = config->ForDefaultInstance();
-
- std::string monitor_socket_name = "modem_simulator";
- std::stringstream ss;
- ss << instance.host_port();
- monitor_socket_name.append(ss.str());
- auto monitor_sock = SharedFD::SocketLocalClient(monitor_socket_name.c_str(),
- true, SOCK_STREAM);
+static bool StopModemSimulator(int id) {
+ std::string socket_name = "modem_simulator" + std::to_string(id);
+ auto monitor_sock =
+ SharedFD::SocketLocalClient(socket_name, true, SOCK_STREAM);
if (!monitor_sock->IsOpen()) {
LOG(ERROR) << "The connection to modem simulator is closed";
return false;
@@ -63,55 +59,83 @@
return true;
}
-std::vector<Command> LaunchModemSimulatorIfEnabled(
- const CuttlefishConfig& config) {
- if (!config.enable_modem_simulator()) {
- LOG(DEBUG) << "Modem simulator not enabled";
+class ModemSimulator : public CommandSource {
+ public:
+ INJECT(ModemSimulator(const CuttlefishConfig& config,
+ const CuttlefishConfig::InstanceSpecific& instance))
+ : config_(config), instance_(instance) {}
+
+ // CommandSource
+ std::vector<Command> Commands() override {
+ Command cmd(ModemSimulatorBinary(), [this](Subprocess* proc) {
+ auto stopped = StopModemSimulator(instance_.modem_simulator_host_id());
+ if (stopped) {
+ return StopperResult::kStopSuccess;
+ }
+ LOG(WARNING) << "Failed to stop modem simulator nicely, "
+ << "attempting to KILL";
+ return KillSubprocess(proc) == StopperResult::kStopSuccess
+ ? StopperResult::kStopCrash
+ : StopperResult::kStopFailure;
+ });
+
+ auto sim_type = config_.modem_simulator_sim_type();
+ cmd.AddParameter(std::string{"-sim_type="} + std::to_string(sim_type));
+ cmd.AddParameter("-server_fds=");
+ bool first_socket = true;
+ for (const auto& socket : sockets_) {
+ if (!first_socket) {
+ cmd.AppendToLastParameter(",");
+ }
+ cmd.AppendToLastParameter(socket);
+ first_socket = false;
+ }
+
+ std::vector<Command> commands;
+ commands.emplace_back(std::move(cmd));
+ return commands;
+ }
+
+ // SetupFeature
+ std::string Name() const override { return "ModemSimulator"; }
+ bool Enabled() const override {
+ if (!config_.enable_modem_simulator()) {
+ LOG(DEBUG) << "Modem simulator not enabled";
+ }
+ return config_.enable_modem_simulator();
+ }
+
+ private:
+ std::unordered_set<SetupFeature*> Dependencies() const override { return {}; }
+ Result<void> ResultSetup() override {
+ int instance_number = config_.modem_simulator_instance_number();
+ CF_EXPECT(instance_number >= 0 && instance_number < 4,
+ "Modem simulator instance number should range between 0 and 3");
+ auto ports = instance_.modem_simulator_ports();
+ for (int i = 0; i < instance_number; ++i) {
+ auto pos = ports.find(',');
+ auto temp = (pos != std::string::npos) ? ports.substr(0, pos) : ports;
+ auto port = std::stoi(temp);
+ ports = ports.substr(pos + 1);
+
+ auto modem_sim_socket = SharedFD::VsockServer(port, SOCK_STREAM);
+ CF_EXPECT(modem_sim_socket->IsOpen(), modem_sim_socket->StrError());
+ sockets_.emplace_back(std::move(modem_sim_socket));
+ }
return {};
}
- int instance_number = config.modem_simulator_instance_number();
- if (instance_number > 3 /* max value */ || instance_number < 0) {
- LOG(ERROR)
- << "Modem simulator instance number should range between 1 and 3";
- return {};
- }
+ const CuttlefishConfig& config_;
+ const CuttlefishConfig::InstanceSpecific& instance_;
+ std::vector<SharedFD> sockets_;
+};
- Command cmd(ModemSimulatorBinary(), [](Subprocess* proc) {
- auto stopped = StopModemSimulator();
- if (stopped) {
- return true;
- }
- LOG(WARNING) << "Failed to stop modem simulator nicely, "
- << "attempting to KILL";
- return KillSubprocess(proc);
- });
-
- auto sim_type = config.modem_simulator_sim_type();
- cmd.AddParameter(std::string{"-sim_type="} + std::to_string(sim_type));
-
- auto instance = config.ForDefaultInstance();
- auto ports = instance.modem_simulator_ports();
- cmd.AddParameter("-server_fds=");
- for (int i = 0; i < instance_number; ++i) {
- auto pos = ports.find(',');
- auto temp = (pos != std::string::npos) ? ports.substr(0, pos) : ports;
- auto port = std::stoi(temp);
- ports = ports.substr(pos + 1);
-
- auto socket = SharedFD::VsockServer(port, SOCK_STREAM);
- CHECK(socket->IsOpen())
- << "Unable to create modem simulator server socket: "
- << socket->StrError();
- if (i > 0) {
- cmd.AppendToLastParameter(",");
- }
- cmd.AppendToLastParameter(socket);
- }
-
- std::vector<Command> commands;
- commands.emplace_back(std::move(cmd));
- return commands;
+fruit::Component<fruit::Required<const CuttlefishConfig,
+ const CuttlefishConfig::InstanceSpecific>>
+launchModemComponent() {
+ return fruit::createComponent()
+ .addMultibinding<CommandSource, ModemSimulator>()
+ .addMultibinding<SetupFeature, ModemSimulator>();
}
} // namespace cuttlefish
diff --git a/host/commands/run_cvd/launch_streamer.cpp b/host/commands/run_cvd/launch_streamer.cpp
index 7ae3f1b..ef9b0ca 100644
--- a/host/commands/run_cvd/launch_streamer.cpp
+++ b/host/commands/run_cvd/launch_streamer.cpp
@@ -16,12 +16,15 @@
#include "host/commands/run_cvd/launch.h"
#include <android-base/logging.h>
+#include <sstream>
#include <string>
#include <utility>
#include "common/libs/fs/shared_buf.h"
#include "common/libs/fs/shared_fd.h"
#include "common/libs/utils/files.h"
+#include "common/libs/utils/result.h"
+#include "host/commands/run_cvd/reporting.h"
#include "host/libs/config/cuttlefish_config.h"
#include "host/libs/config/known_paths.h"
#include "host/libs/vm_manager/crosvm_manager.h"
@@ -41,85 +44,12 @@
return server;
}
-// Creates the frame and input sockets and add the relevant arguments to the vnc
-// server and webrtc commands
-void CreateStreamerServers(Command* cmd, const CuttlefishConfig& config) {
- std::vector<SharedFD> touch_servers;
- SharedFD keyboard_server;
-
- auto instance = config.ForDefaultInstance();
- auto use_vsockets = config.vm_manager() == vm_manager::QemuManager::name();
- for (int i = 0; i < config.display_configs().size(); ++i) {
- touch_servers.push_back(
- use_vsockets
- ? SharedFD::VsockServer(instance.touch_server_port(), SOCK_STREAM)
- : CreateUnixInputServer(instance.touch_socket_path(i)));
- if (!touch_servers.back()->IsOpen()) {
- LOG(ERROR) << "Could not open touch server: "
- << touch_servers.back()->StrError();
- return;
- }
- }
- if (!touch_servers.empty()) {
- cmd->AddParameter("-touch_fds=", touch_servers[0]);
- for (int i = 1; i < touch_servers.size(); ++i) {
- cmd->AppendToLastParameter(",", touch_servers[i]);
- }
- }
-
- if (use_vsockets) {
- cmd->AddParameter("-write_virtio_input");
-
- keyboard_server =
- SharedFD::VsockServer(instance.keyboard_server_port(), SOCK_STREAM);
- } else {
- keyboard_server = CreateUnixInputServer(instance.keyboard_socket_path());
- }
-
- if (!keyboard_server->IsOpen()) {
- LOG(ERROR) << "Could not open keyboard server: "
- << keyboard_server->StrError();
- return;
- }
- cmd->AddParameter("-keyboard_fd=", keyboard_server);
-
- if (config.enable_webrtc() &&
- config.vm_manager() == vm_manager::CrosvmManager::name()) {
- SharedFD switches_server =
- CreateUnixInputServer(instance.switches_socket_path());
- if (!switches_server->IsOpen()) {
- LOG(ERROR) << "Could not open switches server: "
- << switches_server->StrError();
- return;
- }
- cmd->AddParameter("-switches_fd=", switches_server);
- }
-
- SharedFD frames_server = CreateUnixInputServer(instance.frames_socket_path());
- if (!frames_server->IsOpen()) {
- LOG(ERROR) << "Could not open frames server: " << frames_server->StrError();
- return;
- }
- cmd->AddParameter("-frame_server_fd=", frames_server);
-
- if (config.enable_audio()) {
- auto path = config.ForDefaultInstance().audio_server_path();
- auto audio_server =
- SharedFD::SocketLocalServer(path.c_str(), false, SOCK_SEQPACKET, 0666);
- if (!audio_server->IsOpen()) {
- LOG(ERROR) << "Could not create audio server: "
- << audio_server->StrError();
- return;
- }
- cmd->AddParameter("--audio_server_fd=", audio_server);
- }
-}
-
-std::vector<Command> LaunchCustomActionServers(Command& webrtc_cmd,
- const CuttlefishConfig& config) {
+std::vector<Command> LaunchCustomActionServers(
+ Command& webrtc_cmd,
+ const std::vector<CustomActionConfig>& custom_actions) {
bool first = true;
std::vector<Command> commands;
- for (const auto& custom_action : config.custom_actions()) {
+ for (const auto& custom_action : custom_actions) {
if (custom_action.server) {
// Create a socket pair that will be used for communication between
// WebRTC and the action server.
@@ -133,8 +63,8 @@
// Launch the action server, providing its socket pair fd as the only
// argument.
- std::string binary = "bin/" + *(custom_action.server);
- Command command(DefaultHostArtifactsPath(binary));
+ auto binary = HostBinaryPath(*(custom_action.server));
+ Command command(binary);
command.AddParameter(action_server_socket);
commands.emplace_back(std::move(command));
@@ -152,84 +82,224 @@
return commands;
}
+// Creates the frame and input sockets and add the relevant arguments to
+// webrtc commands
+class StreamerSockets : public virtual SetupFeature {
+ public:
+ INJECT(StreamerSockets(const CuttlefishConfig& config,
+ const CuttlefishConfig::InstanceSpecific& instance))
+ : config_(config), instance_(instance) {}
+
+ void AppendCommandArguments(Command& cmd) {
+ if (config_.vm_manager() == vm_manager::QemuManager::name()) {
+ cmd.AddParameter("-write_virtio_input");
+ }
+ if (!touch_servers_.empty()) {
+ cmd.AddParameter("-touch_fds=", touch_servers_[0]);
+ for (int i = 1; i < touch_servers_.size(); ++i) {
+ cmd.AppendToLastParameter(",", touch_servers_[i]);
+ }
+ }
+ cmd.AddParameter("-keyboard_fd=", keyboard_server_);
+ cmd.AddParameter("-frame_server_fd=", frames_server_);
+ if (config_.enable_audio()) {
+ cmd.AddParameter("--audio_server_fd=", audio_server_);
+ }
+ }
+
+ // SetupFeature
+ std::string Name() const override { return "StreamerSockets"; }
+ bool Enabled() const override {
+ bool is_qemu = config_.vm_manager() == vm_manager::QemuManager::name();
+ bool is_accelerated = config_.gpu_mode() != kGpuModeGuestSwiftshader;
+ return !(is_qemu && is_accelerated);
+ }
+
+ private:
+ std::unordered_set<SetupFeature*> Dependencies() const override { return {}; }
+
+ Result<void> ResultSetup() override {
+ auto use_vsockets = config_.vm_manager() == vm_manager::QemuManager::name();
+ for (int i = 0; i < config_.display_configs().size(); ++i) {
+ SharedFD touch_socket =
+ use_vsockets ? SharedFD::VsockServer(instance_.touch_server_port(),
+ SOCK_STREAM)
+ : CreateUnixInputServer(instance_.touch_socket_path(i));
+ CF_EXPECT(touch_socket->IsOpen(), touch_socket->StrError());
+ touch_servers_.emplace_back(std::move(touch_socket));
+ }
+ keyboard_server_ =
+ use_vsockets ? SharedFD::VsockServer(instance_.keyboard_server_port(),
+ SOCK_STREAM)
+ : CreateUnixInputServer(instance_.keyboard_socket_path());
+ CF_EXPECT(keyboard_server_->IsOpen(), keyboard_server_->StrError());
+
+ frames_server_ = CreateUnixInputServer(instance_.frames_socket_path());
+ CF_EXPECT(frames_server_->IsOpen(), frames_server_->StrError());
+ // TODO(schuffelen): Make this a separate optional feature?
+ if (config_.enable_audio()) {
+ auto path = config_.ForDefaultInstance().audio_server_path();
+ audio_server_ =
+ SharedFD::SocketLocalServer(path, false, SOCK_SEQPACKET, 0666);
+ CF_EXPECT(audio_server_->IsOpen(), audio_server_->StrError());
+ }
+ return {};
+ }
+
+ const CuttlefishConfig& config_;
+ const CuttlefishConfig::InstanceSpecific& instance_;
+ std::vector<SharedFD> touch_servers_;
+ SharedFD keyboard_server_;
+ SharedFD frames_server_;
+ SharedFD audio_server_;
+};
+
+class WebRtcServer : public virtual CommandSource,
+ public DiagnosticInformation {
+ public:
+ INJECT(WebRtcServer(const CuttlefishConfig& config,
+ const CuttlefishConfig::InstanceSpecific& instance,
+ StreamerSockets& sockets,
+ KernelLogPipeProvider& log_pipe_provider,
+ const CustomActionConfigProvider& custom_action_config))
+ : config_(config),
+ instance_(instance),
+ sockets_(sockets),
+ log_pipe_provider_(log_pipe_provider),
+ custom_action_config_(custom_action_config) {}
+ // DiagnosticInformation
+ std::vector<std::string> Diagnostics() const override {
+ if (!Enabled() || !config_.ForDefaultInstance().start_webrtc_sig_server()) {
+ // When WebRTC is enabled but an operator other than the one launched by
+ // run_cvd is used there is no way to know the url to which to point the
+ // browser to.
+ return {};
+ }
+ std::ostringstream out;
+ out << "Point your browser to https://" << config_.sig_server_address()
+ << ":" << config_.sig_server_port() << " to interact with the device.";
+ return {out.str()};
+ }
+
+ // CommandSource
+ std::vector<Command> Commands() override {
+ std::vector<Command> commands;
+ if (instance_.start_webrtc_sig_server()) {
+ Command sig_server(WebRtcSigServerBinary());
+ sig_server.AddParameter("-assets_dir=", config_.webrtc_assets_dir());
+ sig_server.AddParameter(
+ "-use_secure_http=",
+ config_.sig_server_secure() ? "true" : "false");
+ if (!config_.webrtc_certs_dir().empty()) {
+ sig_server.AddParameter("-certs_dir=", config_.webrtc_certs_dir());
+ }
+ sig_server.AddParameter("-http_server_port=", config_.sig_server_port());
+ commands.emplace_back(std::move(sig_server));
+ }
+
+ if (instance_.start_webrtc_sig_server_proxy()) {
+ Command sig_proxy(WebRtcSigServerProxyBinary());
+ sig_proxy.AddParameter("-server_port=", config_.sig_server_port());
+ commands.emplace_back(std::move(sig_proxy));
+ }
+
+ auto stopper = [host_socket = std::move(host_socket_)](Subprocess* proc) {
+ struct timeval timeout;
+ timeout.tv_sec = 3;
+ timeout.tv_usec = 0;
+ CHECK(host_socket->SetSockOpt(SOL_SOCKET, SO_RCVTIMEO, &timeout,
+ sizeof(timeout)) == 0)
+ << "Could not set receive timeout";
+
+ WriteAll(host_socket, "C");
+ char response[1];
+ int read_ret = host_socket->Read(response, sizeof(response));
+ if (read_ret != 0) {
+ LOG(ERROR) << "Failed to read response from webrtc";
+ return KillSubprocess(proc);
+ }
+ return KillSubprocess(proc) == StopperResult::kStopSuccess
+ ? StopperResult::kStopCrash
+ : StopperResult::kStopFailure;
+ };
+
+ Command webrtc(WebRtcBinary(), stopper);
+ webrtc.UnsetFromEnvironment("http_proxy");
+ sockets_.AppendCommandArguments(webrtc);
+ if (config_.vm_manager() == vm_manager::CrosvmManager::name()) {
+ webrtc.AddParameter("-switches_fd=", switches_server_);
+ }
+ // Currently there is no way to ensure the signaling server will already
+ // have bound the socket to the port by the time the webrtc process runs
+ // (the common technique of doing it from the launcher is not possible here
+ // as the server library being used creates its own sockets). However, this
+ // issue is mitigated slightly by doing some retrying and backoff in the
+ // webrtc process when connecting to the websocket, so it shouldn't be an
+ // issue most of the time.
+ webrtc.AddParameter("--command_fd=", client_socket_);
+ webrtc.AddParameter("-kernel_log_events_fd=", kernel_log_events_pipe_);
+ webrtc.AddParameter("-client_dir=",
+ DefaultHostArtifactsPath("usr/share/webrtc/assets"));
+
+ // TODO get from launcher params
+ const auto& actions = custom_action_config_.CustomActions();
+ for (auto& action : LaunchCustomActionServers(webrtc, actions)) {
+ commands.emplace_back(std::move(action));
+ }
+ commands.emplace_back(std::move(webrtc));
+
+ return commands;
+ }
+
+ // SetupFeature
+ bool Enabled() const override {
+ return sockets_.Enabled() && config_.enable_webrtc();
+ }
+
+ private:
+ std::string Name() const override { return "WebRtcServer"; }
+ std::unordered_set<SetupFeature*> Dependencies() const override {
+ return {static_cast<SetupFeature*>(&sockets_),
+ static_cast<SetupFeature*>(&log_pipe_provider_)};
+ }
+
+ Result<void> ResultSetup() override {
+ CF_EXPECT(SharedFD::SocketPair(AF_LOCAL, SOCK_STREAM, 0, &client_socket_,
+ &host_socket_),
+ client_socket_->StrError());
+ if (config_.vm_manager() == vm_manager::CrosvmManager::name()) {
+ switches_server_ =
+ CreateUnixInputServer(instance_.switches_socket_path());
+ CF_EXPECT(switches_server_->IsOpen(), switches_server_->StrError());
+ }
+ kernel_log_events_pipe_ = log_pipe_provider_.KernelLogPipe();
+ CF_EXPECT(kernel_log_events_pipe_->IsOpen(),
+ kernel_log_events_pipe_->StrError());
+ return {};
+ }
+
+ const CuttlefishConfig& config_;
+ const CuttlefishConfig::InstanceSpecific& instance_;
+ StreamerSockets& sockets_;
+ KernelLogPipeProvider& log_pipe_provider_;
+ const CustomActionConfigProvider& custom_action_config_;
+ SharedFD kernel_log_events_pipe_;
+ SharedFD client_socket_;
+ SharedFD host_socket_;
+ SharedFD switches_server_;
+};
+
} // namespace
-std::vector<Command> LaunchVNCServer(const CuttlefishConfig& config) {
- auto instance = config.ForDefaultInstance();
- // Launch the vnc server, don't wait for it to complete
- auto port_options = "-port=" + std::to_string(instance.vnc_server_port());
- Command vnc_server(VncServerBinary());
- vnc_server.AddParameter(port_options);
-
- CreateStreamerServers(&vnc_server, config);
-
- std::vector<Command> commands;
- commands.emplace_back(std::move(vnc_server));
- return std::move(commands);
-}
-
-std::vector<Command> LaunchWebRTC(const CuttlefishConfig& config,
- SharedFD kernel_log_events_pipe) {
- std::vector<Command> commands;
- if (config.ForDefaultInstance().start_webrtc_sig_server()) {
- Command sig_server(WebRtcSigServerBinary());
- sig_server.AddParameter("-assets_dir=", config.webrtc_assets_dir());
- if (!config.webrtc_certs_dir().empty()) {
- sig_server.AddParameter("-certs_dir=", config.webrtc_certs_dir());
- }
- sig_server.AddParameter("-http_server_port=", config.sig_server_port());
- commands.emplace_back(std::move(sig_server));
- }
-
- // Currently there is no way to ensure the signaling server will already have
- // bound the socket to the port by the time the webrtc process runs (the
- // common technique of doing it from the launcher is not possible here as the
- // server library being used creates its own sockets). However, this issue is
- // mitigated slightly by doing some retrying and backoff in the webrtc process
- // when connecting to the websocket, so it shouldn't be an issue most of the
- // time.
- SharedFD client_socket;
- SharedFD host_socket;
- CHECK(SharedFD::SocketPair(AF_LOCAL, SOCK_STREAM, 0, &client_socket,
- &host_socket))
- << "Could not open command socket for webRTC";
-
- auto stopper = [host_socket = std::move(host_socket)](Subprocess* proc) {
- struct timeval timeout;
- timeout.tv_sec = 3;
- timeout.tv_usec = 0;
- CHECK(host_socket->SetSockOpt(SOL_SOCKET, SO_RCVTIMEO, &timeout,
- sizeof(timeout)) == 0)
- << "Could not set receive timeout";
-
- WriteAll(host_socket, "C");
- char response[1];
- int read_ret = host_socket->Read(response, sizeof(response));
- if (read_ret != 0) {
- LOG(ERROR) << "Failed to read response from webrtc";
- }
- cuttlefish::KillSubprocess(proc);
- return true;
- };
-
- Command webrtc(WebRtcBinary(), SubprocessStopper(stopper));
-
- webrtc.UnsetFromEnvironment({"http_proxy"});
-
- CreateStreamerServers(&webrtc, config);
-
- webrtc.AddParameter("--command_fd=", client_socket);
- webrtc.AddParameter("-kernel_log_events_fd=", kernel_log_events_pipe);
-
- auto actions = LaunchCustomActionServers(webrtc, config);
-
- // TODO get from launcher params
- commands.emplace_back(std::move(webrtc));
- for (auto& action : actions) {
- commands.emplace_back(std::move(action));
- }
-
- return commands;
+fruit::Component<fruit::Required<const CuttlefishConfig, KernelLogPipeProvider,
+ const CuttlefishConfig::InstanceSpecific,
+ const CustomActionConfigProvider>>
+launchStreamerComponent() {
+ return fruit::createComponent()
+ .addMultibinding<CommandSource, WebRtcServer>()
+ .addMultibinding<DiagnosticInformation, WebRtcServer>()
+ .addMultibinding<SetupFeature, StreamerSockets>()
+ .addMultibinding<SetupFeature, WebRtcServer>();
}
} // namespace cuttlefish
diff --git a/host/commands/run_cvd/main.cc b/host/commands/run_cvd/main.cc
index 944f76c..9332ccc 100644
--- a/host/commands/run_cvd/main.cc
+++ b/host/commands/run_cvd/main.cc
@@ -14,375 +14,223 @@
* limitations under the License.
*/
+#include <android-base/logging.h>
+#include <android-base/strings.h>
+#include <fruit/fruit.h>
+#include <gflags/gflags.h>
#include <unistd.h>
+
#include <fstream>
-#include <iostream>
#include <memory>
#include <string>
#include <utility>
#include <vector>
-#include <android-base/logging.h>
-#include <android-base/strings.h>
-#include <gflags/gflags.h>
-
#include "common/libs/fs/shared_buf.h"
#include "common/libs/fs/shared_fd.h"
#include "common/libs/utils/environment.h"
#include "common/libs/utils/files.h"
-#include "common/libs/utils/network.h"
#include "common/libs/utils/size_utils.h"
#include "common/libs/utils/subprocess.h"
#include "common/libs/utils/tee_logging.h"
#include "host/commands/run_cvd/boot_state_machine.h"
#include "host/commands/run_cvd/launch.h"
#include "host/commands/run_cvd/process_monitor.h"
+#include "host/commands/run_cvd/reporting.h"
#include "host/commands/run_cvd/runner_defs.h"
#include "host/commands/run_cvd/server_loop.h"
+#include "host/commands/run_cvd/validate.h"
+#include "host/libs/config/adb/adb.h"
+#include "host/libs/config/config_flag.h"
+#include "host/libs/config/config_fragment.h"
+#include "host/libs/config/custom_actions.h"
#include "host/libs/config/cuttlefish_config.h"
-#include "host/libs/vm_manager/host_configuration.h"
#include "host/libs/vm_manager/vm_manager.h"
-DEFINE_int32(reboot_notification_fd, -1,
- "A file descriptor to notify when boot completes.");
-
namespace cuttlefish {
-using vm_manager::GetVmManager;
-using vm_manager::ValidateHostConfiguration;
-
namespace {
-constexpr char kGreenColor[] = "\033[1;32m";
-constexpr char kResetColor[] = "\033[0m";
+class CuttlefishEnvironment : public SetupFeature,
+ public DiagnosticInformation {
+ public:
+ INJECT(
+ CuttlefishEnvironment(const CuttlefishConfig& config,
+ const CuttlefishConfig::InstanceSpecific& instance))
+ : config_(config), instance_(instance) {}
-bool WriteCuttlefishEnvironment(const CuttlefishConfig& config) {
- auto env = SharedFD::Open(config.cuttlefish_env_path().c_str(),
- O_CREAT | O_RDWR, 0755);
- if (!env->IsOpen()) {
- LOG(ERROR) << "Unable to create cuttlefish.env file";
- return false;
+ // DiagnosticInformation
+ std::vector<std::string> Diagnostics() const override {
+ auto config_path = instance_.PerInstancePath("cuttlefish_config.json");
+ return {
+ "Launcher log: " + instance_.launcher_log_path(),
+ "Instance configuration: " + config_path,
+ "Instance environment: " + config_.cuttlefish_env_path(),
+ };
}
- auto instance = config.ForDefaultInstance();
- std::string config_env = "export CUTTLEFISH_PER_INSTANCE_PATH=\"" +
- instance.PerInstancePath(".") + "\"\n";
- config_env += "export ANDROID_SERIAL=" + instance.adb_ip_and_port() + "\n";
- env->Write(config_env.c_str(), config_env.size());
- return true;
+
+ // SetupFeature
+ std::string Name() const override { return "CuttlefishEnvironment"; }
+ bool Enabled() const override { return true; }
+
+ private:
+ std::unordered_set<SetupFeature*> Dependencies() const override { return {}; }
+ bool Setup() override {
+ auto env =
+ SharedFD::Open(config_.cuttlefish_env_path(), O_CREAT | O_RDWR, 0755);
+ if (!env->IsOpen()) {
+ LOG(ERROR) << "Unable to create cuttlefish.env file";
+ return false;
+ }
+ std::string config_env = "export CUTTLEFISH_PER_INSTANCE_PATH=\"" +
+ instance_.PerInstancePath(".") + "\"\n";
+ config_env += "export ANDROID_SERIAL=" + instance_.adb_ip_and_port() + "\n";
+ auto written = WriteAll(env, config_env);
+ if (written != config_env.size()) {
+ LOG(ERROR) << "Failed to write all of \"" << config_env << "\", "
+ << "only wrote " << written << " bytes. Error was "
+ << env->StrError();
+ return false;
+ }
+ return true;
+ }
+
+ private:
+ const CuttlefishConfig& config_;
+ const CuttlefishConfig::InstanceSpecific& instance_;
+};
+
+fruit::Component<ServerLoop> runCvdComponent(
+ const CuttlefishConfig* config,
+ const CuttlefishConfig::InstanceSpecific* instance) {
+ return fruit::createComponent()
+ .addMultibinding<DiagnosticInformation, CuttlefishEnvironment>()
+ .addMultibinding<SetupFeature, CuttlefishEnvironment>()
+ .bindInstance(*config)
+ .bindInstance(*instance)
+ .install(AdbConfigComponent)
+ .install(AdbConfigFragmentComponent)
+ .install(bootStateMachineComponent)
+ .install(ConfigFlagPlaceholder)
+ .install(CustomActionsComponent)
+ .install(LaunchAdbComponent)
+ .install(launchComponent)
+ .install(launchModemComponent)
+ .install(launchStreamerComponent)
+ .install(serverLoopComponent)
+ .install(validationComponent)
+ .install(vm_manager::VmManagerComponent);
}
-// Forks and returns the write end of a pipe to the child process. The parent
-// process waits for boot events to come through the pipe and exits accordingly.
-SharedFD DaemonizeLauncher(const CuttlefishConfig& config) {
- auto instance = config.ForDefaultInstance();
- SharedFD read_end, write_end;
- if (!SharedFD::Pipe(&read_end, &write_end)) {
- LOG(ERROR) << "Unable to create pipe";
- return {}; // a closed FD
- }
- auto pid = fork();
- if (pid) {
- // Explicitly close here, otherwise we may end up reading forever if the
- // child process dies.
- write_end->Close();
- RunnerExitCodes exit_code;
- auto bytes_read = read_end->Read(&exit_code, sizeof(exit_code));
- if (bytes_read != sizeof(exit_code)) {
- LOG(ERROR) << "Failed to read a complete exit code, read " << bytes_read
- << " bytes only instead of the expected " << sizeof(exit_code);
- exit_code = RunnerExitCodes::kPipeIOError;
- } else if (exit_code == RunnerExitCodes::kSuccess) {
- LOG(INFO) << "Virtual device booted successfully";
- } else if (exit_code == RunnerExitCodes::kVirtualDeviceBootFailed) {
- LOG(ERROR) << "Virtual device failed to boot";
- } else {
- LOG(ERROR) << "Unexpected exit code: " << exit_code;
- }
- if (exit_code == RunnerExitCodes::kSuccess) {
- LOG(INFO) << kBootCompletedMessage;
- } else {
- LOG(INFO) << kBootFailedMessage;
- }
- std::exit(exit_code);
- } else {
- // The child returns the write end of the pipe
- if (daemon(/*nochdir*/ 1, /*noclose*/ 1) != 0) {
- LOG(ERROR) << "Failed to daemonize child process: " << strerror(errno);
- std::exit(RunnerExitCodes::kDaemonizationError);
- }
- // Redirect standard I/O
- auto log_path = instance.launcher_log_path();
- auto log = SharedFD::Open(log_path.c_str(), O_CREAT | O_WRONLY | O_APPEND,
- S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP);
- if (!log->IsOpen()) {
- LOG(ERROR) << "Failed to create launcher log file: " << log->StrError();
- std::exit(RunnerExitCodes::kDaemonizationError);
- }
- ::android::base::SetLogger(
- TeeLogger({{LogFileSeverity(), log, MetadataLevel::FULL}}));
- auto dev_null = SharedFD::Open("/dev/null", O_RDONLY);
- if (!dev_null->IsOpen()) {
- LOG(ERROR) << "Failed to open /dev/null: " << dev_null->StrError();
- std::exit(RunnerExitCodes::kDaemonizationError);
- }
- if (dev_null->UNMANAGED_Dup2(0) < 0) {
- LOG(ERROR) << "Failed dup2 stdin: " << dev_null->StrError();
- std::exit(RunnerExitCodes::kDaemonizationError);
- }
- if (log->UNMANAGED_Dup2(1) < 0) {
- LOG(ERROR) << "Failed dup2 stdout: " << log->StrError();
- std::exit(RunnerExitCodes::kDaemonizationError);
- }
- if (log->UNMANAGED_Dup2(2) < 0) {
- LOG(ERROR) << "Failed dup2 seterr: " << log->StrError();
- std::exit(RunnerExitCodes::kDaemonizationError);
- }
-
- read_end->Close();
- return write_end;
- }
+Result<void> StdinValid() {
+ CF_EXPECT(!isatty(0),
+ "stdin was a tty, expected to be passed the output of a"
+ " previous stage. Did you mean to run launch_cvd?");
+ CF_EXPECT(errno != EBADF,
+ "stdin was not a valid file descriptor, expected to be passed the "
+ "output of assemble_cvd. Did you mean to run launch_cvd?");
+ return {};
}
-std::string GetConfigFilePath(const CuttlefishConfig& config) {
- auto instance = config.ForDefaultInstance();
- return instance.PerInstancePath("cuttlefish_config.json");
-}
-
-void PrintStreamingInformation(const CuttlefishConfig& config) {
- if (config.ForDefaultInstance().start_webrtc_sig_server()) {
- // TODO (jemoreira): Change this when webrtc is moved to the debian package.
- LOG(INFO) << kGreenColor << "Point your browser to https://"
- << config.sig_server_address() << ":" << config.sig_server_port()
- << " to interact with the device." << kResetColor;
- } else if (config.enable_vnc_server()) {
- LOG(INFO) << kGreenColor << "VNC server started on port "
- << config.ForDefaultInstance().vnc_server_port() << kResetColor;
- }
- // When WebRTC is enabled but an operator other than the one launched by
- // run_cvd is used there is no way to know the url to which to point the
- // browser to.
-}
-
-} // namespace
-
-int RunCvdMain(int argc, char** argv) {
- setenv("ANDROID_LOG_TAGS", "*:v", /* overwrite */ 0);
- ::android::base::InitLogging(argv, android::base::StderrLogger);
- google::ParseCommandLineFlags(&argc, &argv, false);
-
- if (isatty(0)) {
- LOG(FATAL) << "stdin was a tty, expected to be passed the output of a previous stage. "
- << "Did you mean to run launch_cvd?";
- return RunnerExitCodes::kInvalidHostConfiguration;
- } else {
- int error_num = errno;
- if (error_num == EBADF) {
- LOG(FATAL) << "stdin was not a valid file descriptor, expected to be passed the output "
- << "of assemble_cvd. Did you mean to run launch_cvd?";
- return RunnerExitCodes::kInvalidHostConfiguration;
- }
- }
-
+Result<const CuttlefishConfig*> FindConfigFromStdin() {
std::string input_files_str;
{
auto input_fd = SharedFD::Dup(0);
auto bytes_read = ReadAll(input_fd, &input_files_str);
- if (bytes_read < 0) {
- LOG(FATAL) << "Failed to read input files. Error was \"" << input_fd->StrError() << "\"";
- }
+ CF_EXPECT(bytes_read >= 0, "Failed to read input files. Error was \""
+ << input_fd->StrError() << "\"");
}
- std::vector<std::string> input_files = android::base::Split(input_files_str, "\n");
- bool found_config = false;
+ std::vector<std::string> input_files =
+ android::base::Split(input_files_str, "\n");
for (const auto& file : input_files) {
if (file.find("cuttlefish_config.json") != std::string::npos) {
- found_config = true;
setenv(kCuttlefishConfigEnvVarName, file.c_str(), /* overwrite */ false);
}
}
- if (!found_config) {
- return RunnerExitCodes::kCuttlefishConfigurationInitError;
- }
+ return CF_EXPECT(CuttlefishConfig::Get()); // Null check
+}
- auto config = CuttlefishConfig::Get();
- auto instance = config->ForDefaultInstance();
-
+void ConfigureLogs(const CuttlefishConfig& config,
+ const CuttlefishConfig::InstanceSpecific& instance) {
auto log_path = instance.launcher_log_path();
- {
- std::ofstream launcher_log_ofstream(log_path.c_str());
- auto assembly_path = config->AssemblyPath("assemble_cvd.log");
- std::ifstream assembly_log_ifstream(assembly_path);
- if (assembly_log_ifstream) {
- auto assemble_log = ReadFile(assembly_path);
- launcher_log_ofstream << assemble_log;
- }
+ std::ofstream launcher_log_ofstream(log_path.c_str());
+ auto assembly_path = config.AssemblyPath("assemble_cvd.log");
+ std::ifstream assembly_log_ifstream(assembly_path);
+ if (assembly_log_ifstream) {
+ auto assemble_log = ReadFile(assembly_path);
+ launcher_log_ofstream << assemble_log;
}
- ::android::base::SetLogger(LogToStderrAndFiles({log_path}));
+ std::string prefix;
+ if (config.Instances().size() > 1) {
+ prefix = instance.instance_name() + ": ";
+ }
+ ::android::base::SetLogger(LogToStderrAndFiles({log_path}, prefix));
+}
+Result<void> ChdirIntoRuntimeDir(
+ const CuttlefishConfig::InstanceSpecific& instance) {
// Change working directory to the instance directory as early as possible to
// ensure all host processes have the same working dir. This helps stop_cvd
// find the running processes when it can't establish a communication with the
// launcher.
- auto chdir_ret = chdir(instance.instance_dir().c_str());
- if (chdir_ret != 0) {
- auto error = errno;
- LOG(ERROR) << "Unable to change dir into instance directory ("
- << instance.instance_dir() << "): " << strerror(error);
- return RunnerExitCodes::kInstanceDirCreationError;
+ CF_EXPECT(chdir(instance.instance_dir().c_str()) == 0,
+ "Unable to change dir into instance directory \""
+ << instance.instance_dir() << "\": " << strerror(errno));
+ return {};
+}
+
+} // namespace
+
+Result<void> RunCvdMain(int argc, char** argv) {
+ setenv("ANDROID_LOG_TAGS", "*:v", /* overwrite */ 0);
+ ::android::base::InitLogging(argv, android::base::StderrLogger);
+ google::ParseCommandLineFlags(&argc, &argv, false);
+
+ CF_EXPECT(StdinValid(), "Invalid stdin");
+ auto config = CF_EXPECT(FindConfigFromStdin());
+ auto instance = config->ForDefaultInstance();
+
+ ConfigureLogs(*config, instance);
+ CF_EXPECT(ChdirIntoRuntimeDir(instance));
+
+ fruit::Injector<ServerLoop> injector(runCvdComponent, config, &instance);
+
+ for (auto& fragment : injector.getMultibindings<ConfigFragment>()) {
+ CF_EXPECT(config->LoadFragment(*fragment));
}
- auto used_tap_devices = TapInterfacesInUse();
- if (used_tap_devices.count(instance.wifi_tap_name())) {
- LOG(ERROR) << "Wifi TAP device already in use";
- return RunnerExitCodes::kTapDeviceInUse;
- } else if (used_tap_devices.count(instance.mobile_tap_name())) {
- LOG(ERROR) << "Mobile TAP device already in use";
- return RunnerExitCodes::kTapDeviceInUse;
- } else if (used_tap_devices.count(instance.ethernet_tap_name())) {
- LOG(ERROR) << "Ethernet TAP device already in use";
- }
+ // One of the setup features can consume most output, so print this early.
+ DiagnosticInformation::PrintAll(
+ injector.getMultibindings<DiagnosticInformation>());
- auto vm_manager = GetVmManager(config->vm_manager(), config->target_arch());
-
-#ifndef __ANDROID__
- // Check host configuration
- std::vector<std::string> config_commands;
- if (!ValidateHostConfiguration(&config_commands)) {
- LOG(ERROR) << "Validation of user configuration failed";
- std::cout << "Execute the following to correctly configure:" << std::endl;
- for (auto& command : config_commands) {
- std::cout << " " << command << std::endl;
- }
- std::cout << "You may need to logout for the changes to take effect"
- << std::endl;
- return RunnerExitCodes::kInvalidHostConfiguration;
- }
-#endif
-
- if (!WriteCuttlefishEnvironment(*config)) {
- LOG(ERROR) << "Unable to write cuttlefish environment file";
- }
-
- PrintStreamingInformation(*config);
-
- if (config->console()) {
- LOG(INFO) << kGreenColor << "To access the console run: screen "
- << instance.console_path() << kResetColor;
- } else {
- LOG(INFO) << kGreenColor
- << "Serial console is disabled; use -console=true to enable it"
- << kResetColor;
- }
-
- LOG(INFO) << kGreenColor
- << "The following files contain useful debugging information:"
- << kResetColor;
- LOG(INFO) << kGreenColor
- << " Launcher log: " << instance.launcher_log_path()
- << kResetColor;
- LOG(INFO) << kGreenColor
- << " Android's logcat output: " << instance.logcat_path()
- << kResetColor;
- LOG(INFO) << kGreenColor
- << " Kernel log: " << instance.PerInstancePath("kernel.log")
- << kResetColor;
- LOG(INFO) << kGreenColor
- << " Instance configuration: " << GetConfigFilePath(*config)
- << kResetColor;
- LOG(INFO) << kGreenColor
- << " Instance environment: " << config->cuttlefish_env_path()
- << kResetColor;
-
- auto launcher_monitor_path = instance.launcher_monitor_socket_path();
- auto launcher_monitor_socket = SharedFD::SocketLocalServer(
- launcher_monitor_path.c_str(), false, SOCK_STREAM, 0666);
- if (!launcher_monitor_socket->IsOpen()) {
- LOG(ERROR) << "Error when opening launcher server: "
- << launcher_monitor_socket->StrError();
- return RunnerExitCodes::kMonitorCreationFailed;
- }
- SharedFD foreground_launcher_pipe;
- if (config->run_as_daemon()) {
- foreground_launcher_pipe = DaemonizeLauncher(*config);
- if (!foreground_launcher_pipe->IsOpen()) {
- return RunnerExitCodes::kDaemonizationError;
- }
- } else {
- // Make sure the launcher runs in its own process group even when running in
- // foreground
- if (getsid(0) != getpid()) {
- int retval = setpgid(0, 0);
- if (retval) {
- LOG(ERROR) << "Failed to create new process group: " << strerror(errno);
- std::exit(RunnerExitCodes::kProcessGroupError);
- }
- }
- }
-
- SharedFD reboot_notification;
- if (FLAGS_reboot_notification_fd >= 0) {
- reboot_notification = SharedFD::Dup(FLAGS_reboot_notification_fd);
- close(FLAGS_reboot_notification_fd);
- }
+ const auto& features = injector.getMultibindings<SetupFeature>();
+ CF_EXPECT(SetupFeature::RunSetup(features));
// Monitor and restart host processes supporting the CVD
- ProcessMonitor process_monitor(config->restart_subprocesses());
+ ProcessMonitor::Properties process_monitor_properties;
+ process_monitor_properties.RestartSubprocesses(
+ config->restart_subprocesses());
- if (config->enable_metrics() == CuttlefishConfig::kYes) {
- process_monitor.AddCommands(LaunchMetrics());
- }
- process_monitor.AddCommands(LaunchModemSimulatorIfEnabled(*config));
-
- auto kernel_log_monitor = LaunchKernelLogMonitor(*config, 3);
- SharedFD boot_events_pipe = kernel_log_monitor.pipes[0];
- SharedFD adbd_events_pipe = kernel_log_monitor.pipes[1];
- SharedFD webrtc_events_pipe = kernel_log_monitor.pipes[2];
- kernel_log_monitor.pipes.clear();
- process_monitor.AddCommands(std::move(kernel_log_monitor.commands));
-
- CvdBootStateMachine boot_state_machine(foreground_launcher_pipe,
- reboot_notification, boot_events_pipe);
-
- process_monitor.AddCommands(LaunchRootCanal(*config));
- process_monitor.AddCommands(LaunchLogcatReceiver(*config));
- process_monitor.AddCommands(LaunchConfigServer(*config));
- process_monitor.AddCommands(LaunchTombstoneReceiver(*config));
- process_monitor.AddCommands(LaunchGnssGrpcProxyServerIfEnabled(*config));
- process_monitor.AddCommands(LaunchSecureEnvironment(*config));
- if (config->enable_host_bluetooth()) {
- process_monitor.AddCommands(LaunchBluetoothConnector(*config));
- }
- process_monitor.AddCommands(LaunchVehicleHalServerIfEnabled(*config));
- process_monitor.AddCommands(LaunchConsoleForwarderIfEnabled(*config));
-
- // The streamer needs to launch before the VMM because it serves on several
- // sockets (input devices, vsock frame server) when using crosvm.
- if (config->enable_vnc_server()) {
- process_monitor.AddCommands(LaunchVNCServer(*config));
- }
- if (config->enable_webrtc()) {
- process_monitor.AddCommands(LaunchWebRTC(*config, webrtc_events_pipe));
+ for (auto& command_source : injector.getMultibindings<CommandSource>()) {
+ if (command_source->Enabled()) {
+ process_monitor_properties.AddCommands(command_source->Commands());
+ }
}
- // Start the guest VM
- process_monitor.AddCommands(vm_manager->StartCommands(*config));
+ ProcessMonitor process_monitor(std::move(process_monitor_properties));
- // Start other host processes
- process_monitor.AddCommands(
- LaunchSocketVsockProxyIfEnabled(*config, adbd_events_pipe));
- process_monitor.AddCommands(LaunchAdbConnectorIfEnabled(*config));
+ CF_EXPECT(process_monitor.StartAndMonitorProcesses());
- CHECK(process_monitor.StartAndMonitorProcesses())
- << "Could not start subprocesses";
+ injector.get<ServerLoop&>().Run(process_monitor); // Should not return
- ServerLoop(launcher_monitor_socket, &process_monitor); // Should not return
- LOG(ERROR) << "The server loop returned, it should never happen!!";
-
- return RunnerExitCodes::kServerError;
+ return CF_ERR("The server loop returned, it should never happen!!");
}
} // namespace cuttlefish
int main(int argc, char** argv) {
- return cuttlefish::RunCvdMain(argc, argv);
+ auto result = cuttlefish::RunCvdMain(argc, argv);
+ CHECK(result.ok()) << result.error();
+ return 0;
}
diff --git a/host/commands/run_cvd/process_monitor.cc b/host/commands/run_cvd/process_monitor.cc
index a4f7f72..ae720a8 100644
--- a/host/commands/run_cvd/process_monitor.cc
+++ b/host/commands/run_cvd/process_monitor.cc
@@ -26,6 +26,7 @@
#include <stdio.h>
#include <algorithm>
+#include <future>
#include <thread>
#include <android-base/logging.h>
@@ -39,86 +40,82 @@
bool stop;
};
-ProcessMonitor::ProcessMonitor(bool restart_subprocesses)
- : restart_subprocesses_(restart_subprocesses), monitor_(-1) {
+ProcessMonitor::Properties& ProcessMonitor::Properties::RestartSubprocesses(
+ bool r) & {
+ restart_subprocesses_ = r;
+ return *this;
}
-void ProcessMonitor::AddCommand(Command cmd) {
- CHECK(monitor_ == -1) << "The monitor process is already running.";
- CHECK(!monitor_socket_->IsOpen()) << "The monitor socket is already open.";
+ProcessMonitor::Properties ProcessMonitor::Properties::RestartSubprocesses(
+ bool r) && {
+ restart_subprocesses_ = r;
+ return std::move(*this);
+}
- monitored_processes_.push_back(MonitorEntry());
- auto& entry = monitored_processes_.back();
+ProcessMonitor::Properties& ProcessMonitor::Properties::AddCommand(
+ Command cmd) & {
+ auto& entry = entries_.emplace_back();
entry.cmd.reset(new Command(std::move(cmd)));
+ return *this;
}
-bool ProcessMonitor::StopMonitoredProcesses() {
- if (monitor_ == -1) {
- LOG(ERROR) << "The monitor process is already dead.";
- return false;
- }
- if (!monitor_socket_->IsOpen()) {
- LOG(ERROR) << "The monitor socket is already closed.";
- return false;
- }
+ProcessMonitor::Properties ProcessMonitor::Properties::AddCommand(
+ Command cmd) && {
+ auto& entry = entries_.emplace_back();
+ entry.cmd.reset(new Command(std::move(cmd)));
+ return std::move(*this);
+}
+
+ProcessMonitor::ProcessMonitor(ProcessMonitor::Properties&& properties)
+ : properties_(std::move(properties)), monitor_(-1) {}
+
+Result<void> ProcessMonitor::StopMonitoredProcesses() {
+ CF_EXPECT(monitor_ != -1, "The monitor process has already exited.");
+ CF_EXPECT(monitor_socket_->IsOpen(), "The monitor socket is already closed");
ParentToChildMessage message;
message.stop = true;
- if (WriteAllBinary(monitor_socket_, &message) != sizeof(message)) {
- LOG(ERROR) << "Failed to communicate with monitor socket: "
- << monitor_socket_->StrError();
- return false;
- }
+ CF_EXPECT(WriteAllBinary(monitor_socket_, &message) == sizeof(message),
+ "Failed to communicate with monitor socket: "
+ << monitor_socket_->StrError());
+
pid_t last_monitor = monitor_;
monitor_ = -1;
monitor_socket_->Close();
int wstatus;
- if (waitpid(last_monitor, &wstatus, 0) != last_monitor) {
- LOG(ERROR) << "Failed to wait for monitor process";
- return false;
- }
- if (WIFSIGNALED(wstatus)) {
- LOG(ERROR) << "Monitor process exited due to a signal";
- return false;
- }
- if (!WIFEXITED(wstatus)) {
- LOG(ERROR) << "Monitor process exited for unknown reasons";
- return false;
- }
- if (WEXITSTATUS(wstatus) != 0) {
- LOG(ERROR) << "Monitor process exited with code " << WEXITSTATUS(wstatus);
- return false;
- }
- return true;
+ CF_EXPECT(waitpid(last_monitor, &wstatus, 0) == last_monitor,
+ "Failed to wait for monitor process");
+ CF_EXPECT(!WIFSIGNALED(wstatus), "Monitor process exited due to a signal");
+ CF_EXPECT(WIFEXITED(wstatus), "Monitor process exited for unknown reasons");
+ CF_EXPECT(WEXITSTATUS(wstatus) == 0,
+ "Monitor process exited with code " << WEXITSTATUS(wstatus));
+ return {};
}
-bool ProcessMonitor::StartAndMonitorProcesses() {
- if (monitor_ != -1) {
- LOG(ERROR) << "The monitor process was already started";
- return false;
- }
- if (monitor_socket_->IsOpen()) {
- LOG(ERROR) << "The monitor socket was already opened.";
- return false;
- }
+Result<void> ProcessMonitor::StartAndMonitorProcesses() {
+ CF_EXPECT(monitor_ == -1, "The monitor process was already started");
+ CF_EXPECT(!monitor_socket_->IsOpen(), "Monitor socket was already opened");
+
SharedFD client_pipe, host_pipe;
- if (!SharedFD::Pipe(&client_pipe, &host_pipe)) {
- LOG(ERROR) << "Could not create the monitor socket.";
- return false;
- }
+ CF_EXPECT(SharedFD::Pipe(&client_pipe, &host_pipe),
+ "Could not create the monitor socket.");
monitor_ = fork();
if (monitor_ == 0) {
monitor_socket_ = client_pipe;
host_pipe->Close();
- std::exit(MonitorRoutine() ? 0 : 1);
+ auto monitor = MonitorRoutine();
+ if (!monitor.ok()) {
+ LOG(ERROR) << "Monitoring processes failed:\n" << monitor.error();
+ }
+ std::exit(monitor.ok() ? 0 : 1);
} else {
client_pipe->Close();
monitor_socket_ = host_pipe;
- return true;
+ return {};
}
}
static void LogSubprocessExit(const std::string& name, pid_t pid, int wstatus) {
- LOG(INFO) << "Detected exit of monitored subprocess " << name;
+ LOG(INFO) << "Detected unexpected exit of monitored subprocess " << name;
if (WIFEXITED(wstatus)) {
LOG(INFO) << "Subprocess " << name << " (" << pid
<< ") has exited with exit code " << WEXITSTATUS(wstatus);
@@ -131,27 +128,43 @@
}
}
-bool ProcessMonitor::MonitorRoutine() {
+static void LogSubprocessExit(const std::string& name, const siginfo_t& infop) {
+ LOG(INFO) << "Detected unexpected exit of monitored subprocess " << name;
+ if (infop.si_code == CLD_EXITED) {
+ LOG(INFO) << "Subprocess " << name << " (" << infop.si_pid
+ << ") has exited with exit code " << infop.si_status;
+ } else if (infop.si_code == CLD_KILLED) {
+ LOG(ERROR) << "Subprocess " << name << " (" << infop.si_pid
+ << ") was interrupted by a signal: " << infop.si_status;
+ } else {
+ LOG(INFO) << "subprocess " << name << " (" << infop.si_pid
+ << ") has exited for unknown reasons (code = " << infop.si_code
+ << ", status = " << infop.si_status << ")";
+ }
+}
+
+Result<void> ProcessMonitor::MonitorRoutine() {
// Make this process a subreaper to reliably catch subprocess exits.
// See https://man7.org/linux/man-pages/man2/prctl.2.html
prctl(PR_SET_CHILD_SUBREAPER, 1);
prctl(PR_SET_PDEATHSIG, SIGHUP); // Die when parent dies
LOG(DEBUG) << "Starting monitoring subprocesses";
- for (auto& monitored : monitored_processes_) {
- cuttlefish::SubprocessOptions options;
- options.InGroup(true);
+ for (auto& monitored : properties_.entries_) {
+ LOG(INFO) << monitored.cmd->GetShortName();
+ auto options = SubprocessOptions().InGroup(true);
monitored.proc.reset(new Subprocess(monitored.cmd->Start(options)));
- CHECK(monitored.proc->Started()) << "Failed to start process";
+ CF_EXPECT(monitored.proc->Started(), "Failed to start process");
}
bool running = true;
- std::thread parent_comms_thread([&running, this]() {
+ auto policy = std::launch::async;
+ auto parent_comms = std::async(policy, [&running, this]() -> Result<void> {
LOG(DEBUG) << "Waiting for a `stop` message from the parent.";
while (running) {
ParentToChildMessage message;
- CHECK(ReadExactBinary(monitor_socket_, &message) == sizeof(message))
- << "Could not read message from parent.";
+ CF_EXPECT(ReadExactBinary(monitor_socket_, &message) == sizeof(message),
+ "Could not read message from parent.");
if (message.stop) {
running = false;
// Wake up the wait() loop by giving it an exited child process
@@ -160,16 +173,17 @@
}
}
}
+ return {};
});
- auto& monitored = monitored_processes_;
+ auto& monitored = properties_.entries_;
LOG(DEBUG) << "Monitoring subprocesses";
while(running) {
int wstatus;
pid_t pid = wait(&wstatus);
int error_num = errno;
- CHECK(pid != -1) << "Wait failed: " << strerror(error_num);
+ CF_EXPECT(pid != -1, "Wait failed: " << strerror(error_num));
if (!WIFSIGNALED(wstatus) && !WIFEXITED(wstatus)) {
LOG(DEBUG) << "Unexpected status from wait: " << wstatus
<< " for pid " << pid;
@@ -184,35 +198,39 @@
LogSubprocessExit("(unknown)", pid, wstatus);
} else {
LogSubprocessExit(it->cmd->GetShortName(), it->proc->pid(), wstatus);
- if (restart_subprocesses_) {
- cuttlefish::SubprocessOptions options;
- options.InGroup(true);
+ if (properties_.restart_subprocesses_) {
+ auto options = SubprocessOptions().InGroup(true);
it->proc.reset(new Subprocess(it->cmd->Start(options)));
} else {
- monitored_processes_.erase(it);
+ properties_.entries_.erase(it);
}
}
}
- parent_comms_thread.join(); // Should have exited if `running` is false
- // Processes were started in the order they appear in the vector, stop them in
- // reverse order for symmetry.
+ CF_EXPECT(parent_comms.get()); // Should have exited if `running` is false
auto stop = [](const auto& it) {
- if (!it.proc->Stop()) {
+ auto stop_result = it.proc->Stop();
+ if (stop_result == StopperResult::kStopFailure) {
LOG(WARNING) << "Error in stopping \"" << it.cmd->GetShortName() << "\"";
return false;
}
- int wstatus = 0;
- auto ret = it.proc->Wait(&wstatus, 0);
- if (ret < 0) {
+ siginfo_t infop;
+ auto success = it.proc->Wait(&infop, WEXITED);
+ if (success < 0) {
LOG(WARNING) << "Failed to wait for process " << it.cmd->GetShortName();
return false;
}
+ if (stop_result == StopperResult::kStopCrash) {
+ LogSubprocessExit(it.cmd->GetShortName(), infop);
+ }
return true;
};
+ // Processes were started in the order they appear in the vector, stop them in
+ // reverse order for symmetry.
size_t stopped = std::count_if(monitored.rbegin(), monitored.rend(), stop);
LOG(DEBUG) << "Done monitoring subprocesses";
- return stopped == monitored.size();
+ CF_EXPECT(stopped == monitored.size(), "Didn't stop all subprocesses");
+ return {};
}
} // namespace cuttlefish
diff --git a/host/commands/run_cvd/process_monitor.h b/host/commands/run_cvd/process_monitor.h
index 18b8ab8..c3f3127 100644
--- a/host/commands/run_cvd/process_monitor.h
+++ b/host/commands/run_cvd/process_monitor.h
@@ -20,13 +20,11 @@
#include <thread>
#include <vector>
-#include <common/libs/utils/subprocess.h>
+#include "common/libs/utils/result.h"
+#include "common/libs/utils/subprocess.h"
namespace cuttlefish {
-struct MonitorEntry;
-using OnSocketReadyCb = std::function<bool(MonitorEntry*, int)>;
-
struct MonitorEntry {
std::unique_ptr<Command> cmd;
std::unique_ptr<Subprocess> proc;
@@ -35,30 +33,49 @@
// Keeps track of launched subprocesses, restarts them if they unexpectedly exit
class ProcessMonitor {
public:
- ProcessMonitor(bool restart_subprocesses);
- // Adds a command to the list of commands to be run and monitored. The
- // callback will be called when the subprocess has ended. If the callback
- // returns false the subprocess will no longer be monitored. Can only be
- // called before StartAndMonitorProcesses is called. OnSocketReadyCb will be
- // called inside a forked process.
- void AddCommand(Command cmd);
- template <typename T>
- void AddCommands(T&& commands) {
- for (auto& command : commands) {
- AddCommand(std::move(command));
+ class Properties {
+ public:
+ Properties& RestartSubprocesses(bool) &;
+ Properties RestartSubprocesses(bool) &&;
+
+ Properties& AddCommand(Command) &;
+ Properties AddCommand(Command) &&;
+
+ template <typename T>
+ Properties& AddCommands(T commands) & {
+ for (auto& command : commands) {
+ AddCommand(std::move(command));
+ }
+ return *this;
}
- }
+
+ template <typename T>
+ Properties AddCommands(T commands) && {
+ for (auto& command : commands) {
+ AddCommand(std::move(command));
+ }
+ return std::move(*this);
+ }
+
+ private:
+ bool restart_subprocesses_;
+ std::vector<MonitorEntry> entries_;
+
+ friend class ProcessMonitor;
+ };
+ ProcessMonitor(Properties&&);
// Start all processes given by AddCommand.
- bool StartAndMonitorProcesses();
+ Result<void> StartAndMonitorProcesses();
// Stops all monitored subprocesses.
- bool StopMonitoredProcesses();
- private:
- bool MonitorRoutine();
+ Result<void> StopMonitoredProcesses();
- bool restart_subprocesses_;
- std::vector<MonitorEntry> monitored_processes_;
+ private:
+ Result<void> MonitorRoutine();
+
+ Properties properties_;
pid_t monitor_;
SharedFD monitor_socket_;
};
+
} // namespace cuttlefish
diff --git a/host/commands/run_cvd/reporting.cpp b/host/commands/run_cvd/reporting.cpp
new file mode 100644
index 0000000..db25185
--- /dev/null
+++ b/host/commands/run_cvd/reporting.cpp
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "host/commands/run_cvd/reporting.h"
+
+#include <android-base/logging.h>
+#include <fruit/fruit.h>
+#include <string>
+#include <vector>
+
+namespace cuttlefish {
+
+static constexpr char kGreenColor[] = "\033[1;32m";
+static constexpr char kResetColor[] = "\033[0m";
+
+DiagnosticInformation::~DiagnosticInformation() = default;
+
+void DiagnosticInformation::PrintAll(
+ const std::vector<DiagnosticInformation*>& infos) {
+ LOG(INFO) << kGreenColor
+ << "The following files contain useful debugging information:"
+ << kResetColor;
+ for (const auto& info : infos) {
+ for (const auto& line : info->Diagnostics()) {
+ LOG(INFO) << kGreenColor << " " << line << kResetColor;
+ }
+ }
+}
+
+} // namespace cuttlefish
diff --git a/common/libs/utils/size_utils.cpp b/host/commands/run_cvd/reporting.h
similarity index 64%
copy from common/libs/utils/size_utils.cpp
copy to host/commands/run_cvd/reporting.h
index 9f25445..f6b4d5a 100644
--- a/common/libs/utils/size_utils.cpp
+++ b/host/commands/run_cvd/reporting.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2018 The Android Open Source Project
+ * Copyright (C) 2021 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -14,15 +14,20 @@
* limitations under the License.
*/
-#include "common/libs/utils/size_utils.h"
+#pragma once
-#include <unistd.h>
+#include <fruit/fruit.h>
+#include <string>
+#include <vector>
namespace cuttlefish {
-uint64_t AlignToPowerOf2(uint64_t val, uint8_t align_log) {
- uint64_t align = 1ULL << align_log;
- return ((val + (align - 1)) / align) * align;
-}
+class DiagnosticInformation {
+ public:
+ virtual ~DiagnosticInformation();
+ virtual std::vector<std::string> Diagnostics() const = 0;
+
+ static void PrintAll(const std::vector<DiagnosticInformation*>&);
+};
} // namespace cuttlefish
diff --git a/host/commands/run_cvd/server_loop.cpp b/host/commands/run_cvd/server_loop.cpp
index 5e77b9d..dfe98ab 100644
--- a/host/commands/run_cvd/server_loop.cpp
+++ b/host/commands/run_cvd/server_loop.cpp
@@ -16,6 +16,7 @@
#include "host/commands/run_cvd/server_loop.h"
+#include <fruit/fruit.h>
#include <gflags/gflags.h>
#include <unistd.h>
#include <string>
@@ -26,6 +27,7 @@
#include "host/commands/run_cvd/runner_defs.h"
#include "host/libs/config/cuttlefish_config.h"
#include "host/libs/config/data_image.h"
+#include "host/libs/config/feature.h"
namespace cuttlefish {
@@ -47,167 +49,214 @@
return true;
}
-void DeleteFifos(const CuttlefishConfig::InstanceSpecific& instance) {
- // TODO(schuffelen): Create these FIFOs in assemble_cvd instead of run_cvd.
- std::vector<std::string> pipes = {
- instance.kernel_log_pipe_name(),
- instance.console_in_pipe_name(),
- instance.console_out_pipe_name(),
- instance.logcat_pipe_name(),
- instance.PerInstanceInternalPath("keymaster_fifo_vm.in"),
- instance.PerInstanceInternalPath("keymaster_fifo_vm.out"),
- instance.PerInstanceInternalPath("gatekeeper_fifo_vm.in"),
- instance.PerInstanceInternalPath("gatekeeper_fifo_vm.out"),
- instance.PerInstanceInternalPath("bt_fifo_vm.in"),
- instance.PerInstanceInternalPath("bt_fifo_vm.out"),
- };
- for (const auto& pipe : pipes) {
- unlink(pipe.c_str());
- }
-}
+class ServerLoopImpl : public ServerLoop, public SetupFeature {
+ public:
+ INJECT(ServerLoopImpl(const CuttlefishConfig& config,
+ const CuttlefishConfig::InstanceSpecific& instance))
+ : config_(config), instance_(instance) {}
-bool PowerwashFiles() {
- auto config = CuttlefishConfig::Get();
- if (!config) {
- LOG(ERROR) << "Could not load the config.";
- return false;
- }
- auto instance = config->ForDefaultInstance();
-
- DeleteFifos(instance);
-
- // TODO(schuffelen): Clean up duplication with assemble_cvd
- auto kregistry_path = instance.access_kregistry_path();
- unlink(kregistry_path.c_str());
- CreateBlankImage(kregistry_path, 2 /* mb */, "none");
-
- auto pstore_path = instance.pstore_path();
- unlink(pstore_path.c_str());
- CreateBlankImage(pstore_path, 2 /* mb */, "none");
-
- auto sdcard_path = instance.sdcard_path();
- auto sdcard_size = FileSize(sdcard_path);
- unlink(sdcard_path.c_str());
- // round up
- auto sdcard_mb_size = (sdcard_size + (1 << 20) - 1) / (1 << 20);
- LOG(DEBUG) << "Size in mb is " << sdcard_mb_size;
- CreateBlankImage(sdcard_path, sdcard_mb_size, "sdcard");
-
- auto overlay_path = instance.PerInstancePath("overlay.img");
- unlink(overlay_path.c_str());
- if (!CreateQcowOverlay(config->crosvm_binary(),
- instance.os_composite_disk_path(), overlay_path)) {
- LOG(ERROR) << "CreateQcowOverlay failed";
- return false;
- }
- return true;
-}
-
-void RestartRunCvd(const CuttlefishConfig& config, int notification_fd) {
- auto config_path = config.AssemblyPath("cuttlefish_config.json");
- auto followup_stdin = SharedFD::MemfdCreate("pseudo_stdin");
- WriteAll(followup_stdin, config_path + "\n");
- followup_stdin->LSeek(0, SEEK_SET);
- followup_stdin->UNMANAGED_Dup2(0);
-
- auto argv_vec = gflags::GetArgvs();
- char** argv = new char*[argv_vec.size() + 2];
- for (size_t i = 0; i < argv_vec.size(); i++) {
- argv[i] = argv_vec[i].data();
- }
- // Will take precedence over any earlier arguments.
- std::string reboot_notification =
- "-reboot_notification_fd=" + std::to_string(notification_fd);
- argv[argv_vec.size()] = reboot_notification.data();
- argv[argv_vec.size() + 1] = nullptr;
-
- execv("/proc/self/exe", argv);
- // execve should not return, so something went wrong.
- PLOG(ERROR) << "execv returned: ";
-}
-
-} // namespace
-
-void ServerLoop(SharedFD server, ProcessMonitor* process_monitor) {
- while (true) {
- // TODO: use select to handle simultaneous connections.
- auto client = SharedFD::Accept(*server);
- LauncherAction action;
- while (client->IsOpen() && client->Read(&action, sizeof(action)) > 0) {
- switch (action) {
- case LauncherAction::kStop:
- if (process_monitor->StopMonitoredProcesses()) {
+ // ServerLoop
+ void Run(ProcessMonitor& process_monitor) override {
+ while (true) {
+ // TODO: use select to handle simultaneous connections.
+ auto client = SharedFD::Accept(*server_);
+ LauncherAction action;
+ while (client->IsOpen() && client->Read(&action, sizeof(action)) > 0) {
+ switch (action) {
+ case LauncherAction::kStop: {
+ auto stop = process_monitor.StopMonitoredProcesses();
+ if (stop.ok()) {
+ auto response = LauncherResponse::kSuccess;
+ client->Write(&response, sizeof(response));
+ std::exit(0);
+ } else {
+ LOG(ERROR) << "Failed to stop subprocesses:\n" << stop.error();
+ auto response = LauncherResponse::kError;
+ client->Write(&response, sizeof(response));
+ }
+ break;
+ }
+ case LauncherAction::kStatus: {
+ // TODO(schuffelen): Return more information on a side channel
auto response = LauncherResponse::kSuccess;
client->Write(&response, sizeof(response));
- std::exit(0);
- } else {
- auto response = LauncherResponse::kError;
- client->Write(&response, sizeof(response));
- }
- break;
- case LauncherAction::kStatus: {
- // TODO(schuffelen): Return more information on a side channel
- auto response = LauncherResponse::kSuccess;
- client->Write(&response, sizeof(response));
- break;
- }
- case LauncherAction::kPowerwash: {
- LOG(INFO) << "Received a Powerwash request from the monitor socket";
- if (!process_monitor->StopMonitoredProcesses()) {
- LOG(ERROR) << "Stopping processes failed.";
- auto response = LauncherResponse::kError;
- client->Write(&response, sizeof(response));
break;
}
- if (!PowerwashFiles()) {
- LOG(ERROR) << "Powerwashing files failed.";
- auto response = LauncherResponse::kError;
+ case LauncherAction::kPowerwash: {
+ LOG(INFO) << "Received a Powerwash request from the monitor socket";
+ auto stop = process_monitor.StopMonitoredProcesses();
+ if (!stop.ok()) {
+ LOG(ERROR) << "Stopping processes failed:\n" << stop.error();
+ auto response = LauncherResponse::kError;
+ client->Write(&response, sizeof(response));
+ break;
+ }
+ if (!PowerwashFiles()) {
+ LOG(ERROR) << "Powerwashing files failed.";
+ auto response = LauncherResponse::kError;
+ client->Write(&response, sizeof(response));
+ break;
+ }
+ auto response = LauncherResponse::kSuccess;
client->Write(&response, sizeof(response));
+
+ RestartRunCvd(client->UNMANAGED_Dup());
+ // RestartRunCvd should not return, so something went wrong.
+ response = LauncherResponse::kError;
+ client->Write(&response, sizeof(response));
+ LOG(FATAL) << "run_cvd in a bad state";
break;
}
- auto response = LauncherResponse::kSuccess;
- client->Write(&response, sizeof(response));
+ case LauncherAction::kRestart: {
+ auto stop = process_monitor.StopMonitoredProcesses();
+ if (!stop.ok()) {
+ LOG(ERROR) << "Stopping processes failed:\n" << stop.error();
+ auto response = LauncherResponse::kError;
+ client->Write(&response, sizeof(response));
+ break;
+ }
+ DeleteFifos();
- auto config = CuttlefishConfig::Get();
- CHECK(config) << "Could not load config";
- RestartRunCvd(*config, client->UNMANAGED_Dup());
- // RestartRunCvd should not return, so something went wrong.
- response = LauncherResponse::kError;
- client->Write(&response, sizeof(response));
- LOG(FATAL) << "run_cvd in a bad state";
- break;
- }
- case LauncherAction::kRestart: {
- if (!process_monitor->StopMonitoredProcesses()) {
- LOG(ERROR) << "Stopping processes failed.";
- auto response = LauncherResponse::kError;
+ auto response = LauncherResponse::kSuccess;
client->Write(&response, sizeof(response));
+ RestartRunCvd(client->UNMANAGED_Dup());
+ // RestartRunCvd should not return, so something went wrong.
+ response = LauncherResponse::kError;
+ client->Write(&response, sizeof(response));
+ LOG(FATAL) << "run_cvd in a bad state";
break;
}
-
- auto config = CuttlefishConfig::Get();
- CHECK(config) << "Could not load config";
- auto instance = config->ForDefaultInstance();
- DeleteFifos(instance);
-
- auto response = LauncherResponse::kSuccess;
- client->Write(&response, sizeof(response));
- CHECK(config) << "Could not load config";
- RestartRunCvd(*config, client->UNMANAGED_Dup());
- // RestartRunCvd should not return, so something went wrong.
- response = LauncherResponse::kError;
- client->Write(&response, sizeof(response));
- LOG(FATAL) << "run_cvd in a bad state";
- break;
+ default:
+ LOG(ERROR) << "Unrecognized launcher action: "
+ << static_cast<char>(action);
+ auto response = LauncherResponse::kError;
+ client->Write(&response, sizeof(response));
}
- default:
- LOG(ERROR) << "Unrecognized launcher action: "
- << static_cast<char>(action);
- auto response = LauncherResponse::kError;
- client->Write(&response, sizeof(response));
}
}
}
+
+ // SetupFeature
+ std::string Name() const override { return "ServerLoop"; }
+
+ private:
+ bool Enabled() const override { return true; }
+ std::unordered_set<SetupFeature*> Dependencies() const override { return {}; }
+ bool Setup() {
+ auto launcher_monitor_path = instance_.launcher_monitor_socket_path();
+ server_ = SharedFD::SocketLocalServer(launcher_monitor_path.c_str(), false,
+ SOCK_STREAM, 0666);
+ if (!server_->IsOpen()) {
+ LOG(ERROR) << "Error when opening launcher server: "
+ << server_->StrError();
+ return false;
+ }
+ return true;
+ }
+
+ void DeleteFifos() {
+ // TODO(schuffelen): Create these FIFOs in assemble_cvd instead of run_cvd.
+ std::vector<std::string> pipes = {
+ instance_.kernel_log_pipe_name(),
+ instance_.console_in_pipe_name(),
+ instance_.console_out_pipe_name(),
+ instance_.logcat_pipe_name(),
+ instance_.PerInstanceInternalPath("keymaster_fifo_vm.in"),
+ instance_.PerInstanceInternalPath("keymaster_fifo_vm.out"),
+ instance_.PerInstanceInternalPath("gatekeeper_fifo_vm.in"),
+ instance_.PerInstanceInternalPath("gatekeeper_fifo_vm.out"),
+ instance_.PerInstanceInternalPath("bt_fifo_vm.in"),
+ instance_.PerInstanceInternalPath("bt_fifo_vm.out"),
+ instance_.PerInstanceInternalPath("gnsshvc_fifo_vm.in"),
+ instance_.PerInstanceInternalPath("gnsshvc_fifo_vm.out"),
+ instance_.PerInstanceInternalPath("locationhvc_fifo_vm.in"),
+ instance_.PerInstanceInternalPath("locationhvc_fifo_vm.out"),
+ };
+ for (const auto& pipe : pipes) {
+ unlink(pipe.c_str());
+ }
+ }
+
+ bool PowerwashFiles() {
+ DeleteFifos();
+
+ // TODO(schuffelen): Clean up duplication with assemble_cvd
+ unlink(instance_.PerInstancePath("NVChip").c_str());
+
+ auto kregistry_path = instance_.access_kregistry_path();
+ unlink(kregistry_path.c_str());
+ CreateBlankImage(kregistry_path, 2 /* mb */, "none");
+
+ auto hwcomposer_pmem_path = instance_.hwcomposer_pmem_path();
+ unlink(hwcomposer_pmem_path.c_str());
+ CreateBlankImage(hwcomposer_pmem_path, 2 /* mb */, "none");
+
+ auto pstore_path = instance_.pstore_path();
+ unlink(pstore_path.c_str());
+ CreateBlankImage(pstore_path, 2 /* mb */, "none");
+
+ auto sdcard_path = instance_.sdcard_path();
+ auto sdcard_size = FileSize(sdcard_path);
+ unlink(sdcard_path.c_str());
+ // round up
+ auto sdcard_mb_size = (sdcard_size + (1 << 20) - 1) / (1 << 20);
+ LOG(DEBUG) << "Size in mb is " << sdcard_mb_size;
+ CreateBlankImage(sdcard_path, sdcard_mb_size, "sdcard");
+ std::vector<std::string> overlay_files{"overlay.img"};
+ if (instance_.start_ap()) {
+ overlay_files.emplace_back("ap_overlay.img");
+ }
+ for (auto overlay_file : {"overlay.img", "ap_overlay.img"}) {
+ auto overlay_path = instance_.PerInstancePath(overlay_file);
+ unlink(overlay_path.c_str());
+ if (!CreateQcowOverlay(config_.crosvm_binary(),
+ config_.os_composite_disk_path(), overlay_path)) {
+ LOG(ERROR) << "CreateQcowOverlay failed";
+ return false;
+ }
+ }
+ return true;
+ }
+
+ void RestartRunCvd(int notification_fd) {
+ auto config_path = config_.AssemblyPath("cuttlefish_config.json");
+ auto followup_stdin = SharedFD::MemfdCreate("pseudo_stdin");
+ WriteAll(followup_stdin, config_path + "\n");
+ followup_stdin->LSeek(0, SEEK_SET);
+ followup_stdin->UNMANAGED_Dup2(0);
+
+ auto argv_vec = gflags::GetArgvs();
+ char** argv = new char*[argv_vec.size() + 2];
+ for (size_t i = 0; i < argv_vec.size(); i++) {
+ argv[i] = argv_vec[i].data();
+ }
+ // Will take precedence over any earlier arguments.
+ std::string reboot_notification =
+ "-reboot_notification_fd=" + std::to_string(notification_fd);
+ argv[argv_vec.size()] = reboot_notification.data();
+ argv[argv_vec.size() + 1] = nullptr;
+
+ execv("/proc/self/exe", argv);
+ // execve should not return, so something went wrong.
+ PLOG(ERROR) << "execv returned: ";
+ }
+
+ const CuttlefishConfig& config_;
+ const CuttlefishConfig::InstanceSpecific& instance_;
+ SharedFD server_;
+};
+
+} // namespace
+
+ServerLoop::~ServerLoop() = default;
+
+fruit::Component<fruit::Required<const CuttlefishConfig,
+ const CuttlefishConfig::InstanceSpecific>,
+ ServerLoop>
+serverLoopComponent() {
+ return fruit::createComponent()
+ .bind<ServerLoop, ServerLoopImpl>()
+ .addMultibinding<SetupFeature, ServerLoopImpl>();
}
} // namespace cuttlefish
diff --git a/host/commands/run_cvd/server_loop.h b/host/commands/run_cvd/server_loop.h
index 327aff4..2364cb9 100644
--- a/host/commands/run_cvd/server_loop.h
+++ b/host/commands/run_cvd/server_loop.h
@@ -16,11 +16,22 @@
#pragma once
+#include <fruit/fruit.h>
+
#include "common/libs/fs/shared_fd.h"
#include "host/commands/run_cvd/process_monitor.h"
+#include "host/libs/config/cuttlefish_config.h"
namespace cuttlefish {
-void ServerLoop(SharedFD server, ProcessMonitor* process_monitor);
+class ServerLoop {
+ public:
+ virtual ~ServerLoop();
+ virtual void Run(ProcessMonitor& process_monitor) = 0;
+};
+fruit::Component<fruit::Required<const CuttlefishConfig,
+ const CuttlefishConfig::InstanceSpecific>,
+ ServerLoop>
+serverLoopComponent();
}
diff --git a/host/commands/run_cvd/validate.cpp b/host/commands/run_cvd/validate.cpp
new file mode 100644
index 0000000..fb55cbb
--- /dev/null
+++ b/host/commands/run_cvd/validate.cpp
@@ -0,0 +1,98 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <android-base/logging.h>
+#include <fruit/fruit.h>
+#include <iostream>
+
+#include "common/libs/utils/network.h"
+#include "common/libs/utils/result.h"
+#include "host/libs/config/cuttlefish_config.h"
+#include "host/libs/config/feature.h"
+#include "host/libs/vm_manager/host_configuration.h"
+
+namespace cuttlefish {
+namespace {
+
+using vm_manager::ValidateHostConfiguration;
+
+class ValidateTapDevices : public SetupFeature {
+ public:
+ INJECT(ValidateTapDevices(const CuttlefishConfig::InstanceSpecific& instance))
+ : instance_(instance) {}
+
+ std::string Name() const override { return "ValidateTapDevices"; }
+ bool Enabled() const override { return true; }
+
+ private:
+ std::unordered_set<SetupFeature*> Dependencies() const override { return {}; }
+ Result<void> ResultSetup() override {
+ auto taps = TapInterfacesInUse();
+ auto wifi = instance_.wifi_tap_name();
+ CF_EXPECT(taps.count(wifi) == 0, "Device \"" << wifi << "\" in use");
+ auto mobile = instance_.mobile_tap_name();
+ CF_EXPECT(taps.count(mobile) == 0, "Device \"" << mobile << "\" in use");
+ auto eth = instance_.ethernet_tap_name();
+ CF_EXPECT(taps.count(eth) == 0, "Device \"" << eth << "\" in use");
+ return {};
+ }
+
+ private:
+ const CuttlefishConfig::InstanceSpecific& instance_;
+};
+
+class ValidateHostConfigurationFeature : public SetupFeature {
+ public:
+ INJECT(ValidateHostConfigurationFeature()) {}
+
+ bool Enabled() const override {
+#ifndef __ANDROID__
+ return true;
+#else
+ return false;
+#endif
+ }
+ std::string Name() const override { return "ValidateHostConfiguration"; }
+
+ private:
+ std::unordered_set<SetupFeature*> Dependencies() const override { return {}; }
+ bool Setup() override {
+ // Check host configuration
+ std::vector<std::string> config_commands;
+ if (!ValidateHostConfiguration(&config_commands)) {
+ LOG(ERROR) << "Validation of user configuration failed";
+ std::cout << "Execute the following to correctly configure:" << std::endl;
+ for (auto& command : config_commands) {
+ std::cout << " " << command << std::endl;
+ }
+ std::cout << "You may need to logout for the changes to take effect"
+ << std::endl;
+ return false;
+ }
+ return true;
+ }
+};
+
+} // namespace
+
+fruit::Component<fruit::Required<const CuttlefishConfig::InstanceSpecific>>
+validationComponent() {
+ return fruit::createComponent()
+ .addMultibinding<SetupFeature, ValidateHostConfigurationFeature>()
+ .addMultibinding<SetupFeature, ValidateTapDevices>();
+}
+
+} // namespace cuttlefish
diff --git a/common/libs/utils/size_utils.cpp b/host/commands/run_cvd/validate.h
similarity index 63%
copy from common/libs/utils/size_utils.cpp
copy to host/commands/run_cvd/validate.h
index 9f25445..99c5c07 100644
--- a/common/libs/utils/size_utils.cpp
+++ b/host/commands/run_cvd/validate.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2018 The Android Open Source Project
+ * Copyright (C) 2021 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -14,15 +14,17 @@
* limitations under the License.
*/
-#include "common/libs/utils/size_utils.h"
+#pragma once
-#include <unistd.h>
+#include <fruit/fruit.h>
+
+#include "host/libs/config/cuttlefish_config.h"
+#include "host/libs/config/feature.h"
namespace cuttlefish {
-uint64_t AlignToPowerOf2(uint64_t val, uint8_t align_log) {
- uint64_t align = 1ULL << align_log;
- return ((val + (align - 1)) / align) * align;
-}
+fruit::Component<fruit::Required<const CuttlefishConfig,
+ const CuttlefishConfig::InstanceSpecific>>
+validationComponent();
-} // namespace cuttlefish
+}
diff --git a/host/commands/secure_env/Android.bp b/host/commands/secure_env/Android.bp
index 3ceeda5..cd6e5a0 100644
--- a/host/commands/secure_env/Android.bp
+++ b/host/commands/secure_env/Android.bp
@@ -17,10 +17,48 @@
default_applicable_licenses: ["Android-Apache-2.0"],
}
-cc_binary_host {
- name: "secure_env",
+cc_defaults {
+ name: "secure_env_defaults",
+ shared_libs: [
+ "libext2_blkid",
+ "libbase",
+ "libcppbor_external",
+ "libcppcose_rkp",
+ "libcuttlefish_fs",
+ "libcuttlefish_kernel_log_monitor_utils",
+ "libcuttlefish_security",
+ "libcuttlefish_utils",
+ "libfruit",
+ "libgatekeeper",
+ "libjsoncpp",
+ "libkeymaster_portable",
+ "libkeymaster_messages",
+ "libsoft_attestation_cert",
+ "liblog",
+ "libcrypto",
+ "libcutils",
+ "libpuresoftkeymasterdevice_host",
+ "ms-tpm-20-ref-lib",
+ "tpm2-tss2-esys",
+ "tpm2-tss2-mu",
+ "tpm2-tss2-rc",
+ "tpm2-tss2-tcti",
+ ],
+ static_libs: [
+ "libcuttlefish_host_config",
+ "libgflags",
+ "libscrypt_static",
+ ],
+ cflags: [
+ "-fno-rtti", // Required for libkeymaster_portable
+ ],
+}
+
+cc_library_host_static {
+ name: "libsecure_env",
srcs: [
"composite_serialization.cpp",
+ "confui_sign_server.cpp",
"device_tpm.cpp",
"encrypted_serializable.cpp",
"fragile_tpm_storage.cpp",
@@ -42,36 +80,35 @@
"tpm_keymaster_context.cpp",
"tpm_keymaster_enforcement.cpp",
"tpm_random_source.cpp",
+ "tpm_remote_provisioning_context.cpp",
"tpm_resource_manager.cpp",
"tpm_serialize.cpp",
],
- shared_libs: [
- "libbase",
- "libcuttlefish_fs",
- "libcuttlefish_security",
- "libcuttlefish_utils",
- "libgatekeeper",
- "libjsoncpp",
- "libkeymaster_portable",
- "libkeymaster_messages",
- "libsoft_attestation_cert",
- "liblog",
- "libcrypto",
- "libcutils",
- "libpuresoftkeymasterdevice_host",
- "ms-tpm-20-ref-lib",
- "tpm2-tss2-esys",
- "tpm2-tss2-mu",
- "tpm2-tss2-rc",
- "tpm2-tss2-tcti",
+ defaults: ["cuttlefish_buildhost_only", "secure_env_defaults"],
+}
+
+cc_binary_host {
+ name: "secure_env",
+ srcs: [
+ "secure_env.cpp",
],
static_libs: [
- "libcuttlefish_host_config",
- "libgflags",
- "libscrypt_static",
+ "libsecure_env",
],
- defaults: ["cuttlefish_buildhost_only"],
- cflags: [
- "-fno-rtti", // Required for libkeymaster_portable
+ defaults: ["cuttlefish_buildhost_only", "secure_env_defaults"],
+}
+
+cc_test_host {
+ name: "libsecure_env_test",
+ srcs: [
+ "test_tpm.cpp",
+ "encrypted_serializable_test.cpp",
],
+ static_libs: [
+ "libsecure_env",
+ ],
+ defaults: ["cuttlefish_buildhost_only", "secure_env_defaults"],
+ test_options: {
+ unit_test: true,
+ },
}
diff --git a/host/commands/secure_env/composite_serialization.cpp b/host/commands/secure_env/composite_serialization.cpp
index e3d6e43..791a604 100644
--- a/host/commands/secure_env/composite_serialization.cpp
+++ b/host/commands/secure_env/composite_serialization.cpp
@@ -17,6 +17,8 @@
using keymaster::Serializable;
+namespace cuttlefish {
+
CompositeSerializable::CompositeSerializable(
const std::vector<Serializable*>& members) : members_(members) {
}
@@ -46,3 +48,5 @@
}
return true;
}
+
+} // namespace cuttlefish
diff --git a/host/commands/secure_env/composite_serialization.h b/host/commands/secure_env/composite_serialization.h
index dfe2883..c83d159 100644
--- a/host/commands/secure_env/composite_serialization.h
+++ b/host/commands/secure_env/composite_serialization.h
@@ -19,6 +19,8 @@
#include "keymaster/serializable.h"
+namespace cuttlefish {
+
/**
* A keymaster::Serializable type that refers to multiple other
* keymaster::Serializable instances by pointer. When data is serialized or
@@ -37,3 +39,5 @@
private:
std::vector<keymaster::Serializable*> members_;
};
+
+} // namespace cuttlefish
diff --git a/host/commands/secure_env/confui_sign_server.cpp b/host/commands/secure_env/confui_sign_server.cpp
new file mode 100644
index 0000000..618f3e2
--- /dev/null
+++ b/host/commands/secure_env/confui_sign_server.cpp
@@ -0,0 +1,90 @@
+//
+// Copyright (C) 2022 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "confui_sign_server.h"
+
+#include <android-base/logging.h>
+
+#include "host/commands/secure_env/primary_key_builder.h"
+#include "host/commands/secure_env/tpm_hmac.h"
+#include "host/libs/config/cuttlefish_config.h"
+
+namespace cuttlefish {
+ConfUiSignServer::ConfUiSignServer(TpmResourceManager& tpm_resource_manager,
+ SharedFD server_fd)
+ : tpm_resource_manager_(tpm_resource_manager), server_fd_(server_fd) {
+ auto config = cuttlefish::CuttlefishConfig::Get();
+ CHECK(config) << "Config must not be null";
+ auto instance = config->ForDefaultInstance();
+ server_socket_path_ = instance.PerInstanceInternalPath("confui_sign.sock");
+}
+
+[[noreturn]] void ConfUiSignServer::MainLoop() {
+ while (true) {
+ if (!server_fd_->IsOpen()) {
+ server_fd_ = SharedFD::SocketLocalServer(server_socket_path_, false,
+ SOCK_STREAM, 0600);
+ }
+ auto accepted_socket_fd = SharedFD::Accept(*server_fd_);
+ if (!accepted_socket_fd->IsOpen()) {
+ LOG(ERROR) << "Confirmation UI host signing client socket is broken.";
+ continue;
+ }
+ ConfUiSignSender sign_sender(accepted_socket_fd);
+
+ // receive request
+ auto request_opt = sign_sender.Receive();
+ if (!request_opt) {
+ std::string error_category = (sign_sender.IsIoError() ? "IO" : "Logic");
+ LOG(ERROR) << "ReceiveRequest failed with " << error_category << " error";
+ continue;
+ }
+ auto request = request_opt.value();
+
+ // get signing key
+ auto signing_key_builder = PrimaryKeyBuilder();
+ signing_key_builder.SigningKey();
+ signing_key_builder.UniqueData("confirmation_token");
+ auto signing_key = signing_key_builder.CreateKey(tpm_resource_manager_);
+ if (!signing_key) {
+ LOG(ERROR) << "Could not generate signing key";
+ sign_sender.Send(confui::SignMessageError::kUnknownError, {});
+ continue;
+ }
+
+ // hmac
+ auto hmac = TpmHmac(tpm_resource_manager_, signing_key->get(),
+ TpmAuth(ESYS_TR_PASSWORD), request.payload_.data(),
+ request.payload_.size());
+ if (!hmac) {
+ LOG(ERROR) << "Could not calculate confirmation token hmac";
+ sign_sender.Send(confui::SignMessageError::kUnknownError, {});
+ continue;
+ }
+ if (hmac->size == 0) {
+ LOG(ERROR) << "hmac was too short";
+ sign_sender.Send(confui::SignMessageError::kUnknownError, {});
+ continue;
+ }
+
+ // send hmac
+ std::vector<std::uint8_t> hmac_buffer(hmac->buffer,
+ hmac->buffer + hmac->size);
+ if (!sign_sender.Send(confui::SignMessageError::kOk, hmac_buffer)) {
+ LOG(ERROR) << "Sending signature failed likely due to I/O error";
+ }
+ }
+}
+} // namespace cuttlefish
diff --git a/host/commands/secure_env/confui_sign_server.h b/host/commands/secure_env/confui_sign_server.h
new file mode 100644
index 0000000..531a3ed
--- /dev/null
+++ b/host/commands/secure_env/confui_sign_server.h
@@ -0,0 +1,36 @@
+//
+// Copyright (C) 2022 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#pragma once
+
+#include <string>
+
+#include "common/libs/fs/shared_fd.h"
+#include "common/libs/security/confui_sign.h"
+#include "host/commands/secure_env/tpm_resource_manager.h"
+
+namespace cuttlefish {
+class ConfUiSignServer {
+ public:
+ ConfUiSignServer(TpmResourceManager& tpm_resource_manager,
+ SharedFD server_fd);
+ [[noreturn]] void MainLoop();
+
+ private:
+ TpmResourceManager& tpm_resource_manager_;
+ std::string server_socket_path_;
+ SharedFD server_fd_;
+};
+} // end of namespace cuttlefish
diff --git a/host/commands/secure_env/device_tpm.cpp b/host/commands/secure_env/device_tpm.cpp
index 6e4b4be..81ffbf3 100644
--- a/host/commands/secure_env/device_tpm.cpp
+++ b/host/commands/secure_env/device_tpm.cpp
@@ -20,6 +20,8 @@
#include <tss2/tss2_tcti.h>
#include <tss2/tss2_tcti_device.h>
+namespace cuttlefish {
+
static void FinalizeTcti(TSS2_TCTI_CONTEXT* tcti_context) {
if (tcti_context == nullptr) {
return;
@@ -52,3 +54,5 @@
TSS2_TCTI_CONTEXT* DeviceTpm::TctiContext() {
return tpm_.get();
}
+
+} // namespace cuttlefish
diff --git a/host/commands/secure_env/device_tpm.h b/host/commands/secure_env/device_tpm.h
index a52666e..48988ad 100644
--- a/host/commands/secure_env/device_tpm.h
+++ b/host/commands/secure_env/device_tpm.h
@@ -21,6 +21,8 @@
#include "host/commands/secure_env/tpm.h"
+namespace cuttlefish {
+
/*
* Exposes a TSS2_TCTI_CONTEXT for interacting with a TPM device node.
*/
@@ -33,3 +35,5 @@
private:
std::unique_ptr<TSS2_TCTI_CONTEXT, void(*)(TSS2_TCTI_CONTEXT*)> tpm_;
};
+
+} // namespace cuttlefish
diff --git a/host/commands/secure_env/encrypted_serializable.cpp b/host/commands/secure_env/encrypted_serializable.cpp
index 3845212..75891fc 100644
--- a/host/commands/secure_env/encrypted_serializable.cpp
+++ b/host/commands/secure_env/encrypted_serializable.cpp
@@ -16,13 +16,16 @@
#include "encrypted_serializable.h"
#include <vector>
-
+//
#include <android-base/logging.h>
#include "host/commands/secure_env/tpm_auth.h"
#include "host/commands/secure_env/tpm_encrypt_decrypt.h"
+#include "host/commands/secure_env/tpm_random_source.h"
#include "host/commands/secure_env/tpm_serialize.h"
+namespace cuttlefish {
+
EncryptedSerializable::EncryptedSerializable(
TpmResourceManager& resource_manager,
std::function<TpmObjectSlot(TpmResourceManager&)> parent_key_fn,
@@ -172,11 +175,14 @@
SerializeTpmKeyPublic serialize_public(&key_public);
SerializeTpmKeyPrivate serialize_private(&key_private);
auto encrypted_size = RoundUpToBlockSize(wrapped_.SerializedSize());
- return serialize_public.SerializedSize()
- + serialize_private.SerializedSize()
- + sizeof(uint32_t)
- + sizeof(uint32_t)
- + encrypted_size;
+ size_t size = serialize_public.SerializedSize(); // tpm key public part
+ size += serialize_private.SerializedSize(); // tpm key private part
+ size += sizeof(uint32_t); // block size
+ size += sizeof(uint32_t); // initialization vector length
+ size += sizeof(((TPM2B_IV*)nullptr)->buffer); // initialization vector
+ size += sizeof(uint32_t); // wrapped size
+ size += encrypted_size; // encrypted data
+ return size;
}
uint8_t* EncryptedSerializable::Serialize(
@@ -195,6 +201,15 @@
return buf;
}
+ TPM2B_IV iv;
+ iv.size = sizeof(iv.buffer);
+ auto rc = TpmRandomSource(resource_manager_.Esys())
+ .GenerateRandom(iv.buffer, sizeof(iv.buffer));
+ if (rc != KM_ERROR_OK) {
+ LOG(ERROR) << "Failed to get random data";
+ return buf;
+ }
+
auto wrapped_size = wrapped_.SerializedSize();
auto encrypted_size = RoundUpToBlockSize(wrapped_size);
std::vector<uint8_t> unencrypted(encrypted_size + 1, 0);
@@ -206,13 +221,9 @@
return buf;
}
std::vector<uint8_t> encrypted(encrypted_size, 0);
- if (!TpmEncrypt(
- resource_manager_.Esys(),
- key_slot->get(),
- TpmAuth(ESYS_TR_PASSWORD),
- unencrypted.data(),
- encrypted.data(),
- encrypted_size)) {
+ if (!TpmEncrypt( //
+ resource_manager_.Esys(), key_slot->get(), TpmAuth(ESYS_TR_PASSWORD),
+ iv, unencrypted.data(), encrypted.data(), encrypted_size)) {
LOG(ERROR) << "Encryption failed";
return buf;
}
@@ -222,6 +233,8 @@
buf = serialize_public.Serialize(buf, end);
buf = serialize_private.Serialize(buf, end);
buf = keymaster::append_uint32_to_buf(buf, end, BLOCK_SIZE);
+ buf = keymaster::append_uint32_to_buf(buf, end, iv.size);
+ buf = keymaster::append_to_buf(buf, end, iv.buffer, iv.size);
buf = keymaster::append_uint32_to_buf(buf, end, wrapped_size);
buf = keymaster::append_to_buf(buf, end, encrypted.data(), encrypted_size);
return buf;
@@ -262,6 +275,22 @@
<< ", expected " << BLOCK_SIZE;
return false;
}
+ uint32_t iv_size = 0;
+ if (!keymaster::copy_uint32_from_buf(buf_ptr, end, &iv_size)) {
+ LOG(ERROR) << "Failed to read iv size";
+ return false;
+ }
+ TPM2B_IV iv;
+ if (iv_size != sizeof(iv.buffer)) {
+ LOG(ERROR) << "iv size mismatch: received " << iv_size << ", expected "
+ << sizeof(iv.buffer);
+ return false;
+ }
+ iv.size = sizeof(iv.buffer);
+ if (!keymaster::copy_from_buf(buf_ptr, end, iv.buffer, sizeof(iv.buffer))) {
+ LOG(ERROR) << "Failed to read wrapped size";
+ return false;
+ }
uint32_t wrapped_size = 0;
if (!keymaster::copy_uint32_from_buf(buf_ptr, end, &wrapped_size)) {
LOG(ERROR) << "Failed to read wrapped size";
@@ -275,13 +304,9 @@
return false;
}
std::vector<uint8_t> decrypted_data(encrypted_size, 0);
- if (!TpmDecrypt(
- resource_manager_.Esys(),
- key_slot->get(),
- TpmAuth(ESYS_TR_PASSWORD),
- encrypted_data.data(),
- decrypted_data.data(),
- encrypted_size)) {
+ if (!TpmDecrypt( //
+ resource_manager_.Esys(), key_slot->get(), TpmAuth(ESYS_TR_PASSWORD),
+ iv, encrypted_data.data(), decrypted_data.data(), encrypted_size)) {
LOG(ERROR) << "Failed to decrypt encrypted data";
return false;
}
@@ -298,3 +323,5 @@
}
return true;
}
+
+} // namespace cuttlefish
diff --git a/host/commands/secure_env/encrypted_serializable.h b/host/commands/secure_env/encrypted_serializable.h
index c4a2e08..336c40c 100644
--- a/host/commands/secure_env/encrypted_serializable.h
+++ b/host/commands/secure_env/encrypted_serializable.h
@@ -19,6 +19,8 @@
#include "host/commands/secure_env/tpm_resource_manager.h"
+namespace cuttlefish {
+
/**
* A keymaster::Serializable that wraps another keymaster::Serializable,
* encrypting the data with a TPM to ensure privacy.
@@ -57,3 +59,5 @@
std::function<TpmObjectSlot(TpmResourceManager&)> parent_key_fn_;
keymaster::Serializable& wrapped_;
};
+
+} // namespace cuttlefish
diff --git a/host/commands/secure_env/encrypted_serializable_test.cpp b/host/commands/secure_env/encrypted_serializable_test.cpp
new file mode 100644
index 0000000..f1e6923
--- /dev/null
+++ b/host/commands/secure_env/encrypted_serializable_test.cpp
@@ -0,0 +1,55 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "host/commands/secure_env/encrypted_serializable.h"
+
+#include <gtest/gtest.h>
+#include <keymaster/serializable.h>
+#include <string.h>
+
+#include "host/commands/secure_env/primary_key_builder.h"
+#include "host/commands/secure_env/test_tpm.h"
+#include "host/commands/secure_env/tpm_resource_manager.h"
+
+namespace cuttlefish {
+
+TEST(TpmEncryptedSerializable, BinaryData) {
+ TestTpm tpm;
+ TpmResourceManager resource_manager(tpm.Esys());
+
+ uint8_t input_data[] = {1, 2, 3, 4, 5};
+ keymaster::Buffer input(input_data, sizeof(input_data));
+ EncryptedSerializable encrypt_input(resource_manager,
+ ParentKeyCreator("test"), input);
+
+ std::vector<uint8_t> encrypted_data(encrypt_input.SerializedSize());
+ auto encrypt_return = encrypt_input.Serialize(
+ encrypted_data.data(), encrypted_data.data() + encrypted_data.size());
+
+ keymaster::Buffer output(sizeof(input_data));
+ EncryptedSerializable decrypt_intermediate(resource_manager,
+ ParentKeyCreator("test"), output);
+ const uint8_t* encrypted_data_ptr = encrypted_data.data();
+ auto decrypt_return = decrypt_intermediate.Deserialize(
+ &encrypted_data_ptr, encrypted_data_ptr + encrypted_data.size());
+
+ ASSERT_EQ(encrypt_return, encrypted_data.data() + encrypted_data.size());
+ ASSERT_TRUE(decrypt_return);
+ ASSERT_EQ(encrypted_data_ptr, encrypted_data.data() + encrypted_data.size());
+ ASSERT_EQ(0, memcmp(input_data, output.begin(), sizeof(input_data)));
+}
+
+} // namespace cuttlefish
diff --git a/host/commands/secure_env/fragile_tpm_storage.cpp b/host/commands/secure_env/fragile_tpm_storage.cpp
index ae66164..107d895 100644
--- a/host/commands/secure_env/fragile_tpm_storage.cpp
+++ b/host/commands/secure_env/fragile_tpm_storage.cpp
@@ -23,6 +23,8 @@
#include "host/commands/secure_env/json_serializable.h"
#include "host/commands/secure_env/tpm_random_source.h"
+namespace cuttlefish {
+
static constexpr char kEntries[] = "entries";
static constexpr char kKey[] = "key";
static constexpr char kHandle[] = "handle";
@@ -238,3 +240,5 @@
}
return true;
}
+
+} // namespace cuttlefish
diff --git a/host/commands/secure_env/fragile_tpm_storage.h b/host/commands/secure_env/fragile_tpm_storage.h
index b52b373..9a92910 100644
--- a/host/commands/secure_env/fragile_tpm_storage.h
+++ b/host/commands/secure_env/fragile_tpm_storage.h
@@ -26,6 +26,8 @@
#include "host/commands/secure_env/gatekeeper_storage.h"
#include "host/commands/secure_env/tpm_resource_manager.h"
+namespace cuttlefish {
+
/**
* Manager for data stored inside the TPM with an index outside of the TPM. The
* contents of the data cannot be corrupted or decrypted by accessing the index,
@@ -58,3 +60,5 @@
std::string index_file_;
Json::Value index_;
};
+
+} // namespace cuttlefish
diff --git a/host/commands/secure_env/gatekeeper_responder.cpp b/host/commands/secure_env/gatekeeper_responder.cpp
index 7d43a18..756bb72 100644
--- a/host/commands/secure_env/gatekeeper_responder.cpp
+++ b/host/commands/secure_env/gatekeeper_responder.cpp
@@ -18,6 +18,8 @@
#include <android-base/logging.h>
#include <gatekeeper/gatekeeper_messages.h>
+namespace cuttlefish {
+
GatekeeperResponder::GatekeeperResponder(
cuttlefish::GatekeeperChannel& channel, gatekeeper::GateKeeper& gatekeeper)
: channel_(channel), gatekeeper_(gatekeeper) {
@@ -60,3 +62,5 @@
return false;
}
}
+
+} // namespace cuttlefish
diff --git a/host/commands/secure_env/gatekeeper_responder.h b/host/commands/secure_env/gatekeeper_responder.h
index bd27955..fcf7b28 100644
--- a/host/commands/secure_env/gatekeeper_responder.h
+++ b/host/commands/secure_env/gatekeeper_responder.h
@@ -19,6 +19,8 @@
#include "common/libs/security/gatekeeper_channel.h"
+namespace cuttlefish {
+
class GatekeeperResponder {
private:
cuttlefish::GatekeeperChannel& channel_;
@@ -29,3 +31,5 @@
bool ProcessMessage();
};
+
+} // namespace cuttlefish
diff --git a/host/commands/secure_env/gatekeeper_storage.h b/host/commands/secure_env/gatekeeper_storage.h
index d272f71..1882ab0 100644
--- a/host/commands/secure_env/gatekeeper_storage.h
+++ b/host/commands/secure_env/gatekeeper_storage.h
@@ -20,6 +20,8 @@
#include <json/json.h>
#include <tss2/tss2_tpm2_types.h>
+namespace cuttlefish {
+
/**
* Data storage tailored to Gatekeeper's storage needs: storing binary blobs
* that can be destroyed without a trace or corrupted with an obvious trace, but
@@ -40,3 +42,5 @@
virtual bool Write(const Json::Value& key, const TPM2B_MAX_NV_BUFFER& data)
= 0;
};
+
+} // namespace cuttlefish
diff --git a/host/commands/secure_env/hmac_serializable.cpp b/host/commands/secure_env/hmac_serializable.cpp
index c40b736..9366797 100644
--- a/host/commands/secure_env/hmac_serializable.cpp
+++ b/host/commands/secure_env/hmac_serializable.cpp
@@ -1,5 +1,5 @@
//
-// Copyright (C) 2020 The Android Open Source Project
+// cOpyright (C) 2020 The Android Open Source Project
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
@@ -16,20 +16,23 @@
#include "hmac_serializable.h"
#include <android-base/logging.h>
+#include <optional>
+#include <vector>
#include "host/commands/secure_env/tpm_auth.h"
#include "host/commands/secure_env/tpm_hmac.h"
+namespace cuttlefish {
+
HmacSerializable::HmacSerializable(
TpmResourceManager& resource_manager,
std::function<TpmObjectSlot(TpmResourceManager&)> signing_key_fn,
- uint32_t digest_size,
- Serializable* wrapped) :
- resource_manager_(resource_manager),
- signing_key_fn_(signing_key_fn),
- digest_size_(digest_size),
- wrapped_(wrapped) {
-}
+ uint32_t digest_size, Serializable* wrapped, const Serializable* aad)
+ : resource_manager_(resource_manager),
+ signing_key_fn_(signing_key_fn),
+ digest_size_(digest_size),
+ wrapped_(wrapped),
+ aad_(aad) {}
size_t HmacSerializable::SerializedSize() const {
auto digest_size = sizeof(uint32_t) + digest_size_;
@@ -51,13 +54,13 @@
LOG(ERROR) << "Could not retrieve key";
return buf;
}
+ auto maced_data = AppendAad(signed_data, wrapped_size);
+ if (!maced_data) {
+ return buf;
+ }
auto hmac_data =
- TpmHmac(
- resource_manager_,
- key->get(),
- TpmAuth(ESYS_TR_PASSWORD),
- signed_data,
- wrapped_size);
+ TpmHmac(resource_manager_, key->get(), TpmAuth(ESYS_TR_PASSWORD),
+ maced_data->data(), maced_data->size());
if (!hmac_data) {
LOG(ERROR) << "Failed to produce hmac";
return buf;
@@ -99,13 +102,13 @@
LOG(ERROR) << "Could not retrieve key";
return false;
}
+ auto maced_data = AppendAad(signed_data.get(), signed_data_size);
+ if (!maced_data) {
+ return false;
+ }
auto hmac_check =
- TpmHmac(
- resource_manager_,
- key->get(),
- TpmAuth(ESYS_TR_PASSWORD),
- signed_data.get(),
- signed_data_size);
+ TpmHmac(resource_manager_, key->get(), TpmAuth(ESYS_TR_PASSWORD),
+ maced_data->data(), maced_data->size());
if (!hmac_check) {
LOG(ERROR) << "Unable to calculate signature check";
return false;
@@ -125,3 +128,24 @@
return wrapped_->Deserialize(
const_cast<const uint8_t**>(&inner_buf), inner_buf_end);
}
+
+std::optional<std::vector<uint8_t>> HmacSerializable::AppendAad(
+ const uint8_t* sensitive, size_t sensitive_size) const {
+ if (!aad_) {
+ return std::vector<uint8_t>(sensitive, sensitive + sensitive_size);
+ }
+ std::vector<uint8_t> output(sensitive_size + aad_->SerializedSize());
+ std::copy(sensitive, sensitive + sensitive_size, output.begin());
+
+ const uint8_t* actual_output_end =
+ aad_->Serialize(&output[sensitive_size], output.data() + output.size());
+ const ptrdiff_t actual_aad_size = actual_output_end - output.data();
+ if (actual_aad_size != output.size()) {
+ LOG(ERROR) << "Serialized aad did not match expected size. Expected: "
+ << output.size() << ", actual: " << actual_aad_size;
+ return std::nullopt;
+ }
+ return output;
+}
+
+} // namespace cuttlefish
diff --git a/host/commands/secure_env/hmac_serializable.h b/host/commands/secure_env/hmac_serializable.h
index 4b90124..35a6236 100644
--- a/host/commands/secure_env/hmac_serializable.h
+++ b/host/commands/secure_env/hmac_serializable.h
@@ -15,10 +15,15 @@
#pragma once
+#include <optional>
+#include <vector>
+
#include <keymaster/serializable.h>
#include "host/commands/secure_env/tpm_resource_manager.h"
+namespace cuttlefish {
+
/**
* A keymaster::Serializable that wraps another keymaster::Serializable,
* protecting it from tampering while it is stored elsewhere. This stores
@@ -38,17 +43,23 @@
*/
class HmacSerializable : public keymaster::Serializable {
public:
- HmacSerializable(TpmResourceManager&,
- std::function<TpmObjectSlot(TpmResourceManager&)>,
- uint32_t digest_size,
- Serializable*);
+ HmacSerializable(TpmResourceManager&,
+ std::function<TpmObjectSlot(TpmResourceManager&)>,
+ uint32_t digest_size, Serializable*, const Serializable* aad);
- size_t SerializedSize() const override;
- uint8_t* Serialize(uint8_t* buf, const uint8_t* end) const override;
- bool Deserialize(const uint8_t** buf_ptr, const uint8_t* end) override;
+ size_t SerializedSize() const override;
+ uint8_t* Serialize(uint8_t* buf, const uint8_t* end) const override;
+ bool Deserialize(const uint8_t** buf_ptr, const uint8_t* end) override;
+
private:
TpmResourceManager& resource_manager_;
std::function<TpmObjectSlot(TpmResourceManager&)> signing_key_fn_;
uint32_t digest_size_;
- keymaster::Serializable* wrapped_;
+ Serializable* wrapped_;
+ const Serializable* aad_;
+
+ std::optional<std::vector<uint8_t>> AppendAad(const uint8_t* sensitive,
+ size_t sensitive_size) const;
};
+
+} // namespace cuttlefish
diff --git a/host/commands/secure_env/in_process_tpm.cpp b/host/commands/secure_env/in_process_tpm.cpp
index f240c08..551283e 100644
--- a/host/commands/secure_env/in_process_tpm.cpp
+++ b/host/commands/secure_env/in_process_tpm.cpp
@@ -37,13 +37,18 @@
#include <android-base/logging.h>
+#include <mutex>
+
+namespace cuttlefish {
+
struct __attribute__((__packed__)) tpm_message_header {
uint16_t tag;
uint32_t length;
uint32_t ordinal;
};
-struct InProcessTpm::Impl {
+class InProcessTpm::Impl {
+ public:
static Impl* FromContext(TSS2_TCTI_CONTEXT* context) {
auto offset = offsetof(Impl, tcti_context_);
char* context_char = reinterpret_cast<char*>(context);
@@ -94,63 +99,88 @@
return TSS2_RC_SUCCESS;
}
+ Impl() {
+ {
+ std::lock_guard<std::mutex> lock(global_mutex);
+ // This is a limitation of ms-tpm-20-ref
+ CHECK(!global_instance) << "InProcessTpm internally uses global data, so "
+ << "only one can exist.";
+ global_instance = this;
+ }
+
+ tcti_context_.v1.magic = 0xFAD;
+ tcti_context_.v1.version = 1;
+ tcti_context_.v1.transmit = Impl::Transmit;
+ tcti_context_.v1.receive = Impl::Receive;
+ _plat__NVEnable(NULL);
+ if (_plat__NVNeedsManufacture()) {
+ // Can't use android logging here due to a macro conflict with TPM
+ // internals
+ LOG(DEBUG) << "Manufacturing TPM state";
+ if (TPM_Manufacture(1)) {
+ LOG(FATAL) << "Failed to manufacture TPM state";
+ }
+ }
+ _rpc__Signal_PowerOn(false);
+ _rpc__Signal_NvOn();
+
+ ESYS_CONTEXT* esys = nullptr;
+ auto rc = Esys_Initialize(&esys, TctiContext(), nullptr);
+ if (rc != TPM2_RC_SUCCESS) {
+ LOG(FATAL) << "Could not initialize esys: " << Tss2_RC_Decode(rc) << " ("
+ << rc << ")";
+ }
+
+ rc = Esys_Startup(esys, TPM2_SU_CLEAR);
+ if (rc != TPM2_RC_SUCCESS) {
+ LOG(FATAL) << "TPM2_Startup failed: " << Tss2_RC_Decode(rc) << " (" << rc
+ << ")";
+ }
+
+ TPM2B_AUTH auth = {};
+ Esys_TR_SetAuth(esys, ESYS_TR_RH_LOCKOUT, &auth);
+
+ rc = Esys_DictionaryAttackLockReset(
+ /* esysContext */ esys,
+ /* lockHandle */ ESYS_TR_RH_LOCKOUT,
+ /* shandle1 */ ESYS_TR_PASSWORD,
+ /* shandle2 */ ESYS_TR_NONE,
+ /* shandle3 */ ESYS_TR_NONE);
+
+ if (rc != TPM2_RC_SUCCESS) {
+ LOG(FATAL) << "Could not reset TPM lockout: " << Tss2_RC_Decode(rc)
+ << " (" << rc << ")";
+ }
+
+ Esys_Finalize(&esys);
+ }
+
+ ~Impl() {
+ _rpc__Signal_NvOff();
+ _rpc__Signal_PowerOff();
+ std::lock_guard<std::mutex> lock(global_mutex);
+ global_instance = nullptr;
+ }
+
+ TSS2_TCTI_CONTEXT* TctiContext() {
+ return reinterpret_cast<TSS2_TCTI_CONTEXT*>(&tcti_context_);
+ }
+
+ private:
+ static std::mutex global_mutex;
+ static Impl* global_instance;
TSS2_TCTI_CONTEXT_COMMON_CURRENT tcti_context_;
std::list<std::vector<uint8_t>> command_queue_;
std::mutex queue_mutex_;
};
-InProcessTpm::InProcessTpm() : impl_(new Impl()) {
- impl_->tcti_context_.v1.magic = 0xFAD;
- impl_->tcti_context_.v1.version = 1;
- impl_->tcti_context_.v1.transmit = Impl::Transmit;
- impl_->tcti_context_.v1.receive = Impl::Receive;
- _plat__NVEnable(NULL);
- if (_plat__NVNeedsManufacture()) {
- // Can't use android logging here due to a macro conflict with TPM internals
- LOG(DEBUG) << "Manufacturing TPM state";
- if (TPM_Manufacture(1)) {
- LOG(FATAL) << "Failed to manufacture TPM state";
- }
- }
- _rpc__Signal_PowerOn(false);
- _rpc__Signal_NvOn();
+std::mutex InProcessTpm::Impl::global_mutex;
+InProcessTpm::Impl* InProcessTpm::Impl::global_instance;
- ESYS_CONTEXT* esys = nullptr;
- auto rc = Esys_Initialize(&esys, TctiContext(), nullptr);
- if (rc != TPM2_RC_SUCCESS) {
- LOG(FATAL) << "Could not initialize esys: " << Tss2_RC_Decode(rc)
- << " (" << rc << ")";
- }
+InProcessTpm::InProcessTpm() : impl_(new Impl()) {}
- rc = Esys_Startup(esys, TPM2_SU_CLEAR);
- if (rc != TPM2_RC_SUCCESS) {
- LOG(FATAL) << "TPM2_Startup failed: " << Tss2_RC_Decode(rc)
- << " (" << rc << ")";
- }
+InProcessTpm::~InProcessTpm() = default;
- TPM2B_AUTH auth = {};
- Esys_TR_SetAuth(esys, ESYS_TR_RH_LOCKOUT, &auth);
+TSS2_TCTI_CONTEXT* InProcessTpm::TctiContext() { return impl_->TctiContext(); }
- rc = Esys_DictionaryAttackLockReset(
- /* esysContext */ esys,
- /* lockHandle */ ESYS_TR_RH_LOCKOUT,
- /* shandle1 */ ESYS_TR_PASSWORD,
- /* shandle2 */ ESYS_TR_NONE,
- /* shandle3 */ ESYS_TR_NONE);
-
- if (rc != TPM2_RC_SUCCESS) {
- LOG(FATAL) << "Could not reset TPM lockout: " << Tss2_RC_Decode(rc)
- << " (" << rc << ")";
- }
-
- Esys_Finalize(&esys);
-}
-
-InProcessTpm::~InProcessTpm() {
- _rpc__Signal_NvOff();
- _rpc__Signal_PowerOff();
-}
-
-TSS2_TCTI_CONTEXT* InProcessTpm::TctiContext() {
- return reinterpret_cast<TSS2_TCTI_CONTEXT*>(&impl_->tcti_context_);
-}
+} // namespace cuttlefish
diff --git a/host/commands/secure_env/in_process_tpm.h b/host/commands/secure_env/in_process_tpm.h
index 35fecfb..624e7c4 100644
--- a/host/commands/secure_env/in_process_tpm.h
+++ b/host/commands/secure_env/in_process_tpm.h
@@ -23,6 +23,8 @@
#include "host/commands/secure_env/tpm.h"
+namespace cuttlefish {
+
/*
* Exposes a TSS2_TCTI_CONTEXT for interacting with an in-process TPM simulator.
*
@@ -41,7 +43,9 @@
TSS2_TCTI_CONTEXT* TctiContext() override;
private:
- struct Impl;
+ class Impl;
- std::unique_ptr<Impl> impl_;
+ std::unique_ptr<Impl> impl_;
};
+
+} // namespace cuttlefish
diff --git a/host/commands/secure_env/insecure_fallback_storage.cpp b/host/commands/secure_env/insecure_fallback_storage.cpp
index f906bd5..643a3ce 100644
--- a/host/commands/secure_env/insecure_fallback_storage.cpp
+++ b/host/commands/secure_env/insecure_fallback_storage.cpp
@@ -23,6 +23,8 @@
#include "host/commands/secure_env/json_serializable.h"
#include "host/commands/secure_env/tpm_random_source.h"
+namespace cuttlefish {
+
static constexpr char kEntries[] = "entries";
static constexpr char kKey[] = "key";
static constexpr char kValue[] = "value";
@@ -147,3 +149,5 @@
}
return true;
}
+
+} // namespace cuttlefish
diff --git a/host/commands/secure_env/insecure_fallback_storage.h b/host/commands/secure_env/insecure_fallback_storage.h
index 93788a0..64405be 100644
--- a/host/commands/secure_env/insecure_fallback_storage.h
+++ b/host/commands/secure_env/insecure_fallback_storage.h
@@ -23,6 +23,8 @@
#include "host/commands/secure_env/gatekeeper_storage.h"
#include "host/commands/secure_env/tpm_resource_manager.h"
+namespace cuttlefish {
+
/**
* A GatekeeperStorage fallback implementation that is less secure. It uses an
* index file that is signed and encrypted by the TPM and the sensitive data
@@ -54,3 +56,5 @@
std::string index_file_;
Json::Value index_;
};
+
+} // namespace cuttlefish
diff --git a/host/commands/secure_env/json_serializable.cpp b/host/commands/secure_env/json_serializable.cpp
index d4f2fd8..630fb529 100644
--- a/host/commands/secure_env/json_serializable.cpp
+++ b/host/commands/secure_env/json_serializable.cpp
@@ -24,6 +24,8 @@
#include "host/commands/secure_env/hmac_serializable.h"
#include "host/commands/secure_env/primary_key_builder.h"
+namespace cuttlefish {
+
static constexpr char kUniqueKey[] = "JsonSerializable";
class JsonSerializable : public keymaster::Serializable {
@@ -89,8 +91,9 @@
EncryptedSerializable encryption(
resource_manager, parent_key_fn, sensitive_material);
auto signing_key_fn = SigningKeyCreator(kUniqueKey);
- HmacSerializable sign_check(
- resource_manager, signing_key_fn, TPM2_SHA256_DIGEST_SIZE, &encryption);
+ HmacSerializable sign_check(resource_manager, signing_key_fn,
+ TPM2_SHA256_DIGEST_SIZE, &encryption,
+ /*aad=*/nullptr);
auto size = sign_check.SerializedSize();
LOG(INFO) << "size : " << size;
@@ -139,8 +142,9 @@
EncryptedSerializable encryption(
resource_manager, parent_key_fn, sensitive_material);
auto signing_key_fn = SigningKeyCreator(kUniqueKey);
- HmacSerializable sign_check(
- resource_manager, signing_key_fn, TPM2_SHA256_DIGEST_SIZE, &encryption);
+ HmacSerializable sign_check(resource_manager, signing_key_fn,
+ TPM2_SHA256_DIGEST_SIZE, &encryption,
+ /*aad=*/nullptr);
auto buf = reinterpret_cast<const uint8_t*>(buffer.data());
auto buf_end = buf + buffer.size();
@@ -151,3 +155,5 @@
return json;
}
+
+} // namespace cuttlefish
diff --git a/host/commands/secure_env/json_serializable.h b/host/commands/secure_env/json_serializable.h
index f96aab3..984f396 100644
--- a/host/commands/secure_env/json_serializable.h
+++ b/host/commands/secure_env/json_serializable.h
@@ -19,7 +19,11 @@
#include "host/commands/secure_env/tpm_resource_manager.h"
+namespace cuttlefish {
+
bool WriteProtectedJsonToFile(
TpmResourceManager&, const std::string& filename, Json::Value);
Json::Value ReadProtectedJsonFromFile(
TpmResourceManager&, const std::string& filename);
+
+} // namespace cuttlefish
diff --git a/host/commands/secure_env/keymaster_responder.cpp b/host/commands/secure_env/keymaster_responder.cpp
index a564254..688ddf3 100644
--- a/host/commands/secure_env/keymaster_responder.cpp
+++ b/host/commands/secure_env/keymaster_responder.cpp
@@ -18,10 +18,11 @@
#include <android-base/logging.h>
#include <keymaster/android_keymaster_messages.h>
-KeymasterResponder::KeymasterResponder(
- cuttlefish::KeymasterChannel& channel, keymaster::AndroidKeymaster& keymaster)
- : channel_(channel), keymaster_(keymaster) {
-}
+namespace cuttlefish {
+
+KeymasterResponder::KeymasterResponder(cuttlefish::KeymasterChannel& channel,
+ keymaster::AndroidKeymaster& keymaster)
+ : channel_(channel), keymaster_(keymaster) {}
bool KeymasterResponder::ProcessMessage() {
auto request = channel_.ReceiveMessage();
@@ -31,19 +32,19 @@
}
const uint8_t* buffer = request->payload;
const uint8_t* end = request->payload + request->payload_size;
- switch(request->cmd) {
+ switch (request->cmd) {
using namespace keymaster;
-#define HANDLE_MESSAGE(ENUM_NAME, METHOD_NAME) \
- case ENUM_NAME: {\
- METHOD_NAME##Request request(keymaster_.message_version()); \
- if (!request.Deserialize(&buffer, end)) { \
- LOG(ERROR) << "Failed to deserialize " #METHOD_NAME "Request"; \
- return false; \
- } \
- METHOD_NAME##Response response(keymaster_.message_version()); \
- keymaster_.METHOD_NAME(request, &response); \
- return channel_.SendResponse(ENUM_NAME, response); \
- }
+#define HANDLE_MESSAGE(ENUM_NAME, METHOD_NAME) \
+ case ENUM_NAME: { \
+ METHOD_NAME##Request request(keymaster_.message_version()); \
+ if (!request.Deserialize(&buffer, end)) { \
+ LOG(ERROR) << "Failed to deserialize " #METHOD_NAME "Request"; \
+ return false; \
+ } \
+ METHOD_NAME##Response response(keymaster_.message_version()); \
+ keymaster_.METHOD_NAME(request, &response); \
+ return channel_.SendResponse(ENUM_NAME, response); \
+ }
HANDLE_MESSAGE(GENERATE_KEY, GenerateKey)
HANDLE_MESSAGE(BEGIN_OPERATION, BeginOperation)
HANDLE_MESSAGE(UPDATE_OPERATION, UpdateOperation)
@@ -65,38 +66,48 @@
HANDLE_MESSAGE(DELETE_KEY, DeleteKey)
HANDLE_MESSAGE(DELETE_ALL_KEYS, DeleteAllKeys)
HANDLE_MESSAGE(IMPORT_WRAPPED_KEY, ImportWrappedKey)
+ HANDLE_MESSAGE(GENERATE_RKP_KEY, GenerateRkpKey)
+ HANDLE_MESSAGE(GENERATE_CSR, GenerateCsr)
HANDLE_MESSAGE(GENERATE_TIMESTAMP_TOKEN, GenerateTimestampToken)
#undef HANDLE_MESSAGE
-#define HANDLE_MESSAGE_W_RETURN(ENUM_NAME, METHOD_NAME) \
- case ENUM_NAME: {\
- METHOD_NAME##Request request(keymaster_.message_version()); \
- if (!request.Deserialize(&buffer, end)) { \
- LOG(ERROR) << "Failed to deserialize " #METHOD_NAME "Request"; \
- return false; \
- } \
- auto response = keymaster_.METHOD_NAME(request); \
- return channel_.SendResponse(ENUM_NAME, response); \
- }
+#define HANDLE_MESSAGE_W_RETURN(ENUM_NAME, METHOD_NAME) \
+ case ENUM_NAME: { \
+ METHOD_NAME##Request request(keymaster_.message_version()); \
+ if (!request.Deserialize(&buffer, end)) { \
+ LOG(ERROR) << "Failed to deserialize " #METHOD_NAME "Request"; \
+ return false; \
+ } \
+ auto response = keymaster_.METHOD_NAME(request); \
+ return channel_.SendResponse(ENUM_NAME, response); \
+ }
HANDLE_MESSAGE_W_RETURN(COMPUTE_SHARED_HMAC, ComputeSharedHmac)
HANDLE_MESSAGE_W_RETURN(VERIFY_AUTHORIZATION, VerifyAuthorization)
HANDLE_MESSAGE_W_RETURN(DEVICE_LOCKED, DeviceLocked)
HANDLE_MESSAGE_W_RETURN(GET_VERSION_2, GetVersion2)
-#undef HANDLE_MESSAGE
+ HANDLE_MESSAGE_W_RETURN(CONFIGURE_VENDOR_PATCHLEVEL,
+ ConfigureVendorPatchlevel)
+ HANDLE_MESSAGE_W_RETURN(CONFIGURE_BOOT_PATCHLEVEL, ConfigureBootPatchlevel)
+ HANDLE_MESSAGE_W_RETURN(CONFIGURE_VERIFIED_BOOT_INFO,
+ ConfigureVerifiedBootInfo)
+ HANDLE_MESSAGE_W_RETURN(GET_ROOT_OF_TRUST, GetRootOfTrust)
+#undef HANDLE_MESSAGE_W_RETURN
#define HANDLE_MESSAGE_W_RETURN_NO_ARG(ENUM_NAME, METHOD_NAME) \
- case ENUM_NAME: {\
- auto response = keymaster_.METHOD_NAME(); \
- return channel_.SendResponse(ENUM_NAME, response); \
- }
- HANDLE_MESSAGE_W_RETURN_NO_ARG(GET_HMAC_SHARING_PARAMETERS, GetHmacSharingParameters)
+ case ENUM_NAME: { \
+ auto response = keymaster_.METHOD_NAME(); \
+ return channel_.SendResponse(ENUM_NAME, response); \
+ }
+ HANDLE_MESSAGE_W_RETURN_NO_ARG(GET_HMAC_SHARING_PARAMETERS,
+ GetHmacSharingParameters)
HANDLE_MESSAGE_W_RETURN_NO_ARG(EARLY_BOOT_ENDED, EarlyBootEnded)
-#undef HANDLE_MESSAGE
+#undef HANDLE_MESSAGE_W_RETURN_NO_ARG
case ADD_RNG_ENTROPY: {
AddEntropyRequest request(keymaster_.message_version());
if (!request.Deserialize(&buffer, end)) {
LOG(ERROR) << "Failed to deserialize AddEntropyRequest";
return false;
}
- AddEntropyResponse response(keymaster_.message_version());;
+ AddEntropyResponse response(keymaster_.message_version());
+ ;
keymaster_.AddRngEntropy(request, &response);
return channel_.SendResponse(ADD_RNG_ENTROPY, response);
}
@@ -107,3 +118,5 @@
return false;
}
}
+
+} // namespace cuttlefish
diff --git a/host/commands/secure_env/keymaster_responder.h b/host/commands/secure_env/keymaster_responder.h
index f8fb6ec..2bbd893 100644
--- a/host/commands/secure_env/keymaster_responder.h
+++ b/host/commands/secure_env/keymaster_responder.h
@@ -19,6 +19,8 @@
#include "common/libs/security/keymaster_channel.h"
+namespace cuttlefish {
+
class KeymasterResponder {
private:
cuttlefish::KeymasterChannel& channel_;
@@ -29,3 +31,5 @@
bool ProcessMessage();
};
+
+} // namespace cuttlefish
diff --git a/host/commands/secure_env/primary_key_builder.cpp b/host/commands/secure_env/primary_key_builder.cpp
index a44ff24..b333002 100644
--- a/host/commands/secure_env/primary_key_builder.cpp
+++ b/host/commands/secure_env/primary_key_builder.cpp
@@ -19,6 +19,8 @@
#include <tss2/tss2_mu.h>
#include <tss2/tss2_rc.h>
+namespace cuttlefish {
+
PrimaryKeyBuilder::PrimaryKeyBuilder() : public_area_({}) {
public_area_.nameAlg = TPM2_ALG_SHA256;
};
@@ -133,3 +135,5 @@
return key_builder.CreateKey(resource_manager);
};
}
+
+} // namespace cuttlefish
diff --git a/host/commands/secure_env/primary_key_builder.h b/host/commands/secure_env/primary_key_builder.h
index 46e5461..70170e7 100644
--- a/host/commands/secure_env/primary_key_builder.h
+++ b/host/commands/secure_env/primary_key_builder.h
@@ -22,6 +22,8 @@
#include "host/commands/secure_env/tpm_resource_manager.h"
+namespace cuttlefish {
+
class PrimaryKeyBuilder {
public:
PrimaryKeyBuilder();
@@ -40,3 +42,5 @@
std::function<TpmObjectSlot(TpmResourceManager&)>
ParentKeyCreator(const std::string& unique);
+
+} // namespace cuttlefish
diff --git a/host/commands/secure_env/proxy_keymaster_context.h b/host/commands/secure_env/proxy_keymaster_context.h
new file mode 100644
index 0000000..e3bf426
--- /dev/null
+++ b/host/commands/secure_env/proxy_keymaster_context.h
@@ -0,0 +1,162 @@
+//
+// Copyright (C) 2020 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#pragma once
+
+#include <map>
+#include <vector>
+
+#include <keymaster/key.h>
+#include <keymaster/keymaster_context.h>
+#include <keymaster/km_openssl/attestation_record.h>
+
+#include "tpm_attestation_record.h"
+
+namespace cuttlefish {
+
+class TpmAttestationRecordContext;
+class TpmResourceManager;
+class TpmKeyBlobMaker;
+class TpmRandomSource;
+class TpmRemoteProvisioningContext;
+
+/**
+ * Implementation of KeymasterContext that proxies to another implementation.
+ *
+ * Because AndroidKeymaster wraps a KeymasterContext and puts it into a unique
+ * pointer, it doesn't let the implementor manage the lifetime of the
+ * KeymasterContext implementation. This proxy breaks that relationship, and
+ * allows the lifetimes to be distinct as long as the KeymasterContext instance
+ * outlives the AndroidKeymaster instance.
+ */
+class ProxyKeymasterContext : public keymaster::KeymasterContext {
+ public:
+ ProxyKeymasterContext(KeymasterContext& wrapped) : wrapped_(wrapped) {}
+ ~ProxyKeymasterContext() = default;
+
+ keymaster::KmVersion GetKmVersion() const override {
+ return wrapped_.GetKmVersion();
+ }
+
+ keymaster_error_t SetSystemVersion(uint32_t os_version,
+ uint32_t os_patchlevel) override {
+ return wrapped_.SetSystemVersion(os_version, os_patchlevel);
+ }
+ void GetSystemVersion(uint32_t* os_version,
+ uint32_t* os_patchlevel) const override {
+ return wrapped_.GetSystemVersion(os_version, os_patchlevel);
+ }
+
+ const keymaster::KeyFactory* GetKeyFactory(
+ keymaster_algorithm_t algorithm) const override {
+ return wrapped_.GetKeyFactory(algorithm);
+ }
+ const keymaster::OperationFactory* GetOperationFactory(
+ keymaster_algorithm_t algorithm,
+ keymaster_purpose_t purpose) const override {
+ return wrapped_.GetOperationFactory(algorithm, purpose);
+ }
+ const keymaster_algorithm_t* GetSupportedAlgorithms(
+ size_t* algorithms_count) const override {
+ return wrapped_.GetSupportedAlgorithms(algorithms_count);
+ }
+
+ keymaster_error_t UpgradeKeyBlob(
+ const keymaster::KeymasterKeyBlob& key_to_upgrade,
+ const keymaster::AuthorizationSet& upgrade_params,
+ keymaster::KeymasterKeyBlob* upgraded_key) const override {
+ return wrapped_.UpgradeKeyBlob(key_to_upgrade, upgrade_params,
+ upgraded_key);
+ }
+
+ keymaster_error_t ParseKeyBlob(
+ const keymaster::KeymasterKeyBlob& blob,
+ const keymaster::AuthorizationSet& additional_params,
+ keymaster::UniquePtr<keymaster::Key>* key) const override {
+ return wrapped_.ParseKeyBlob(blob, additional_params, key);
+ }
+
+ keymaster_error_t AddRngEntropy(const uint8_t* buf,
+ size_t length) const override {
+ return wrapped_.AddRngEntropy(buf, length);
+ }
+
+ keymaster::KeymasterEnforcement* enforcement_policy() override {
+ return wrapped_.enforcement_policy();
+ }
+
+ keymaster::AttestationContext* attestation_context() override {
+ return wrapped_.attestation_context();
+ }
+
+ keymaster::CertificateChain GenerateAttestation(
+ const keymaster::Key& key,
+ const keymaster::AuthorizationSet& attest_params,
+ keymaster::UniquePtr<keymaster::Key> attest_key,
+ const keymaster::KeymasterBlob& issuer_subject,
+ keymaster_error_t* error) const override {
+ return wrapped_.GenerateAttestation(
+ key, attest_params, std::move(attest_key), issuer_subject, error);
+ }
+
+ keymaster::CertificateChain GenerateSelfSignedCertificate(
+ const keymaster::Key& key, const keymaster::AuthorizationSet& cert_params,
+ bool fake_signature, keymaster_error_t* error) const override {
+ return wrapped_.GenerateSelfSignedCertificate(key, cert_params,
+ fake_signature, error);
+ }
+
+ keymaster_error_t UnwrapKey(
+ const keymaster::KeymasterKeyBlob& wrapped_key_blob,
+ const keymaster::KeymasterKeyBlob& wrapping_key_blob,
+ const keymaster::AuthorizationSet& wrapping_key_params,
+ const keymaster::KeymasterKeyBlob& masking_key,
+ keymaster::AuthorizationSet* wrapped_key_params,
+ keymaster_key_format_t* wrapped_key_format,
+ keymaster::KeymasterKeyBlob* wrapped_key_material) const override {
+ return wrapped_.UnwrapKey(
+ wrapped_key_blob, wrapping_key_blob, wrapping_key_params, masking_key,
+ wrapped_key_params, wrapped_key_format, wrapped_key_material);
+ }
+
+ keymaster::RemoteProvisioningContext* GetRemoteProvisioningContext()
+ const override {
+ return wrapped_.GetRemoteProvisioningContext();
+ }
+
+ keymaster_error_t SetVendorPatchlevel(uint32_t vendor_patchlevel) override {
+ return wrapped_.SetVendorPatchlevel(vendor_patchlevel);
+ }
+ keymaster_error_t SetBootPatchlevel(uint32_t boot_patchlevel) override {
+ return wrapped_.SetBootPatchlevel(boot_patchlevel);
+ }
+ keymaster_error_t SetVerifiedBootInfo(
+ std::string_view verified_boot_state, std::string_view bootloader_state,
+ const std::vector<uint8_t>& vbmeta_digest) {
+ return wrapped_.SetVerifiedBootInfo(verified_boot_state, bootloader_state,
+ vbmeta_digest);
+ }
+ std::optional<uint32_t> GetVendorPatchlevel() const override {
+ return wrapped_.GetVendorPatchlevel();
+ }
+ std::optional<uint32_t> GetBootPatchlevel() const override {
+ return wrapped_.GetBootPatchlevel();
+ }
+
+ private:
+ KeymasterContext& wrapped_;
+};
+
+} // namespace cuttlefish
diff --git a/host/commands/secure_env/secure_env.cpp b/host/commands/secure_env/secure_env.cpp
index c050c58..97dd48c 100644
--- a/host/commands/secure_env/secure_env.cpp
+++ b/host/commands/secure_env/secure_env.cpp
@@ -16,22 +16,28 @@
#include <thread>
#include <android-base/logging.h>
+#include <fruit/fruit.h>
#include <gflags/gflags.h>
#include <keymaster/android_keymaster.h>
-#include <keymaster/soft_keymaster_logger.h>
#include <keymaster/contexts/pure_soft_keymaster_context.h>
+#include <keymaster/soft_keymaster_logger.h>
#include <tss2/tss2_esys.h>
#include <tss2/tss2_rc.h>
#include "common/libs/fs/shared_fd.h"
+#include "common/libs/security/confui_sign.h"
#include "common/libs/security/gatekeeper_channel.h"
#include "common/libs/security/keymaster_channel.h"
+#include "host/commands/kernel_log_monitor/kernel_log_server.h"
+#include "host/commands/kernel_log_monitor/utils.h"
+#include "host/commands/secure_env/confui_sign_server.h"
#include "host/commands/secure_env/device_tpm.h"
#include "host/commands/secure_env/fragile_tpm_storage.h"
#include "host/commands/secure_env/gatekeeper_responder.h"
-#include "host/commands/secure_env/insecure_fallback_storage.h"
#include "host/commands/secure_env/in_process_tpm.h"
+#include "host/commands/secure_env/insecure_fallback_storage.h"
#include "host/commands/secure_env/keymaster_responder.h"
+#include "host/commands/secure_env/proxy_keymaster_context.h"
#include "host/commands/secure_env/soft_gatekeeper.h"
#include "host/commands/secure_env/tpm_gatekeeper.h"
#include "host/commands/secure_env/tpm_keymaster_context.h"
@@ -39,13 +45,16 @@
#include "host/commands/secure_env/tpm_resource_manager.h"
#include "host/libs/config/logging.h"
-// Copied from AndroidKeymaster4Device
-constexpr size_t kOperationTableSize = 16;
-
+DEFINE_int32(confui_server_fd, -1, "A named socket to serve confirmation UI");
DEFINE_int32(keymaster_fd_in, -1, "A pipe for keymaster communication");
DEFINE_int32(keymaster_fd_out, -1, "A pipe for keymaster communication");
DEFINE_int32(gatekeeper_fd_in, -1, "A pipe for gatekeeper communication");
DEFINE_int32(gatekeeper_fd_out, -1, "A pipe for gatekeeper communication");
+DEFINE_int32(kernel_events_fd, -1,
+ "A pipe for monitoring events based on "
+ "messages written to the kernel log. This "
+ "is used by secure_env to monitor for "
+ "device reboots.");
DEFINE_string(tpm_impl,
"in_memory",
@@ -57,106 +66,175 @@
DEFINE_string(gatekeeper_impl, "tpm",
"The gatekeeper implementation. \"tpm\" or \"software\"");
-int main(int argc, char** argv) {
- cuttlefish::DefaultSubprocessLogging(argv);
+namespace cuttlefish {
+namespace {
+
+// Copied from AndroidKeymaster4Device
+constexpr size_t kOperationTableSize = 16;
+
+// Dup a command line file descriptor into a SharedFD.
+SharedFD DupFdFlag(gflags::int32 fd) {
+ CHECK(fd != -1);
+ SharedFD duped = SharedFD::Dup(fd);
+ CHECK(duped->IsOpen()) << "Could not dup output fd: " << duped->StrError();
+ // The original FD is intentionally kept open so that we can re-exec this
+ // process without having to do a bunch of argv book-keeping.
+ return duped;
+}
+
+// Re-launch this process with all the same flags it was originallys started
+// with.
+[[noreturn]] void ReExecSelf() {
+ // Allocate +1 entry for terminating nullptr.
+ std::vector<char*> argv(gflags::GetArgvs().size() + 1, nullptr);
+ for (size_t i = 0; i < gflags::GetArgvs().size(); ++i) {
+ argv[i] = strdup(gflags::GetArgvs()[i].c_str());
+ CHECK(argv[i] != nullptr) << "OOM";
+ }
+ execv("/proc/self/exe", argv.data());
+ char buf[128];
+ LOG(FATAL) << "Exec failed, secure_env is out of sync with the guest: "
+ << errno << "(" << strerror_r(errno, buf, sizeof(buf)) << ")";
+ abort(); // LOG(FATAL) isn't marked as noreturn
+}
+
+// Spin up a thread that monitors for a kernel loaded event, then re-execs
+// this process. This way, secure_env's boot tracking matches up with the guest.
+std::thread StartKernelEventMonitor(SharedFD kernel_events_fd) {
+ return std::thread([kernel_events_fd]() {
+ while (kernel_events_fd->IsOpen()) {
+ auto read_result = monitor::ReadEvent(kernel_events_fd);
+ CHECK(read_result.has_value()) << kernel_events_fd->StrError();
+ if (read_result->event == monitor::Event::BootloaderLoaded) {
+ LOG(DEBUG) << "secure_env detected guest reboot, restarting.";
+ ReExecSelf();
+ }
+ }
+ });
+}
+
+fruit::Component<fruit::Required<gatekeeper::SoftGateKeeper, TpmGatekeeper,
+ TpmResourceManager>,
+ gatekeeper::GateKeeper, keymaster::KeymasterEnforcement>
+ChooseGatekeeperComponent() {
+ if (FLAGS_gatekeeper_impl == "software") {
+ return fruit::createComponent()
+ .bind<gatekeeper::GateKeeper, gatekeeper::SoftGateKeeper>()
+ .registerProvider([]() -> keymaster::KeymasterEnforcement* {
+ return new keymaster::SoftKeymasterEnforcement(64, 64);
+ });
+ } else if (FLAGS_gatekeeper_impl == "tpm") {
+ return fruit::createComponent()
+ .bind<gatekeeper::GateKeeper, TpmGatekeeper>()
+ .registerProvider(
+ [](TpmResourceManager& resource_manager,
+ TpmGatekeeper& gatekeeper) -> keymaster::KeymasterEnforcement* {
+ return new TpmKeymasterEnforcement(resource_manager, gatekeeper);
+ });
+ } else {
+ LOG(FATAL) << "Invalid gatekeeper implementation: "
+ << FLAGS_gatekeeper_impl;
+ abort();
+ }
+}
+
+fruit::Component<TpmResourceManager, gatekeeper::GateKeeper,
+ keymaster::KeymasterEnforcement>
+SecureEnvComponent() {
+ return fruit::createComponent()
+ .registerProvider([]() -> Tpm* { // fruit will take ownership
+ if (FLAGS_tpm_impl == "in_memory") {
+ return new InProcessTpm();
+ } else if (FLAGS_tpm_impl == "host_device") {
+ return new DeviceTpm("/dev/tpm0");
+ } else {
+ LOG(FATAL) << "Unknown TPM implementation: " << FLAGS_tpm_impl;
+ abort();
+ }
+ })
+ .registerProvider([](Tpm* tpm) {
+ if (tpm->TctiContext() == nullptr) {
+ LOG(FATAL) << "Unable to connect to TPM implementation.";
+ }
+ ESYS_CONTEXT* esys_ptr = nullptr;
+ std::unique_ptr<ESYS_CONTEXT, void (*)(ESYS_CONTEXT*)> esys(
+ nullptr, [](ESYS_CONTEXT* esys) { Esys_Finalize(&esys); });
+ auto rc = Esys_Initialize(&esys_ptr, tpm->TctiContext(), nullptr);
+ if (rc != TPM2_RC_SUCCESS) {
+ LOG(FATAL) << "Could not initialize esys: " << Tss2_RC_Decode(rc)
+ << " (" << rc << ")";
+ }
+ esys.reset(esys_ptr);
+ return esys;
+ })
+ .registerProvider(
+ [](std::unique_ptr<ESYS_CONTEXT, void (*)(ESYS_CONTEXT*)>& esys) {
+ return new TpmResourceManager(
+ esys.get()); // fruit will take ownership
+ })
+ .registerProvider([](TpmResourceManager& resource_manager) {
+ return new FragileTpmStorage(resource_manager, "gatekeeper_secure");
+ })
+ .registerProvider([](TpmResourceManager& resource_manager) {
+ return new InsecureFallbackStorage(resource_manager,
+ "gatekeeper_insecure");
+ })
+ .registerProvider([](TpmResourceManager& resource_manager,
+ FragileTpmStorage& secure_storage,
+ InsecureFallbackStorage& insecure_storage) {
+ return new TpmGatekeeper(resource_manager, secure_storage,
+ insecure_storage);
+ })
+ .registerProvider([]() { return new gatekeeper::SoftGateKeeper(); })
+ .install(ChooseGatekeeperComponent);
+}
+
+} // namespace
+
+int SecureEnvMain(int argc, char** argv) {
+ DefaultSubprocessLogging(argv);
gflags::ParseCommandLineFlags(&argc, &argv, true);
keymaster::SoftKeymasterLogger km_logger;
- std::unique_ptr<Tpm> tpm;
- if (FLAGS_tpm_impl == "in_memory") {
- tpm.reset(new InProcessTpm());
- } else if (FLAGS_tpm_impl == "host_device") {
- tpm.reset(new DeviceTpm("/dev/tpm0"));
- } else {
- LOG(FATAL) << "Unknown TPM implementation: " << FLAGS_tpm_impl;
- }
+ fruit::Injector<TpmResourceManager, gatekeeper::GateKeeper,
+ keymaster::KeymasterEnforcement>
+ injector(SecureEnvComponent);
+ TpmResourceManager* resource_manager = injector.get<TpmResourceManager*>();
+ gatekeeper::GateKeeper* gatekeeper = injector.get<gatekeeper::GateKeeper*>();
+ keymaster::KeymasterEnforcement* keymaster_enforcement =
+ injector.get<keymaster::KeymasterEnforcement*>();
- if (tpm->TctiContext() == nullptr) {
- LOG(FATAL) << "Unable to connect to TPM implementation.";
- }
-
- std::unique_ptr<TpmResourceManager> resource_manager;
- std::unique_ptr<ESYS_CONTEXT, void(*)(ESYS_CONTEXT*)> esys(
- nullptr, [](ESYS_CONTEXT* esys) { Esys_Finalize(&esys); });
- if (FLAGS_keymint_impl == "tpm" || FLAGS_gatekeeper_impl == "tpm") {
- ESYS_CONTEXT* esys_ptr = nullptr;
- auto rc = Esys_Initialize(&esys_ptr, tpm->TctiContext(), nullptr);
- if (rc != TPM2_RC_SUCCESS) {
- LOG(FATAL) << "Could not initialize esys: " << Tss2_RC_Decode(rc)
- << " (" << rc << ")";
- }
- esys.reset(esys_ptr);
- resource_manager.reset(new TpmResourceManager(esys.get()));
- }
-
- std::unique_ptr<GatekeeperStorage> secure_storage;
- std::unique_ptr<GatekeeperStorage> insecure_storage;
- std::unique_ptr<gatekeeper::GateKeeper> gatekeeper;
- std::unique_ptr<keymaster::KeymasterEnforcement> keymaster_enforcement;
- if (FLAGS_gatekeeper_impl == "software") {
- gatekeeper.reset(new gatekeeper::SoftGateKeeper);
- keymaster_enforcement.reset(
- new keymaster::SoftKeymasterEnforcement(64, 64));
- } else if (FLAGS_gatekeeper_impl == "tpm") {
- secure_storage.reset(
- new FragileTpmStorage(*resource_manager, "gatekeeper_secure"));
- insecure_storage.reset(
- new InsecureFallbackStorage(*resource_manager, "gatekeeper_insecure"));
- TpmGatekeeper* tpm_gatekeeper =
- new TpmGatekeeper(*resource_manager, *secure_storage, *insecure_storage);
- gatekeeper.reset(tpm_gatekeeper);
- keymaster_enforcement.reset(
- new TpmKeymasterEnforcement(*resource_manager, *tpm_gatekeeper));
- }
-
- // keymaster::AndroidKeymaster puts the given pointer into a UniquePtr,
- // taking ownership.
- keymaster::KeymasterContext* keymaster_context;
+ std::unique_ptr<keymaster::KeymasterContext> keymaster_context;
if (FLAGS_keymint_impl == "software") {
// TODO: See if this is the right KM version.
- keymaster_context =
- new keymaster::PureSoftKeymasterContext(keymaster::KmVersion::KEYMASTER_4,
- KM_SECURITY_LEVEL_SOFTWARE);
+ keymaster_context.reset(new keymaster::PureSoftKeymasterContext(
+ keymaster::KmVersion::KEYMINT_2, KM_SECURITY_LEVEL_SOFTWARE));
} else if (FLAGS_keymint_impl == "tpm") {
- keymaster_context =
- new TpmKeymasterContext(*resource_manager, *keymaster_enforcement);
+ keymaster_context.reset(
+ new TpmKeymasterContext(*resource_manager, *keymaster_enforcement));
} else {
LOG(FATAL) << "Unknown keymaster implementation " << FLAGS_keymint_impl;
return -1;
}
+ // keymaster::AndroidKeymaster puts the context pointer into a UniquePtr,
+ // taking ownership.
keymaster::AndroidKeymaster keymaster{
- keymaster_context, kOperationTableSize,
- keymaster::MessageVersion(keymaster::KmVersion::KEYMINT_1,
+ new ProxyKeymasterContext(*keymaster_context), kOperationTableSize,
+ keymaster::MessageVersion(keymaster::KmVersion::KEYMINT_2,
0 /* km_date */)};
- CHECK(FLAGS_keymaster_fd_in != -1);
- auto keymaster_in = cuttlefish::SharedFD::Dup(FLAGS_keymaster_fd_in);
- CHECK(keymaster_in->IsOpen()) << "Could not dup input fd: "
- << keymaster_in->StrError();
- close(FLAGS_keymaster_fd_in);
+ auto confui_server_fd = DupFdFlag(FLAGS_confui_server_fd);
+ auto keymaster_in = DupFdFlag(FLAGS_keymaster_fd_in);
+ auto keymaster_out = DupFdFlag(FLAGS_keymaster_fd_out);
+ auto gatekeeper_in = DupFdFlag(FLAGS_gatekeeper_fd_in);
+ auto gatekeeper_out = DupFdFlag(FLAGS_gatekeeper_fd_out);
+ auto kernel_events_fd = DupFdFlag(FLAGS_kernel_events_fd);
- CHECK(FLAGS_keymaster_fd_out != -1);
- auto keymaster_out = cuttlefish::SharedFD::Dup(FLAGS_keymaster_fd_out);
- CHECK(keymaster_out->IsOpen()) << "Could not dup output fd: "
- << keymaster_out->StrError();
- close(FLAGS_keymaster_fd_out);
+ std::vector<std::thread> threads;
- CHECK(FLAGS_gatekeeper_fd_in != -1);
- auto gatekeeper_in = cuttlefish::SharedFD::Dup(FLAGS_gatekeeper_fd_in);
- CHECK(gatekeeper_in->IsOpen()) << "Could not dup input fd: "
- << gatekeeper_in->StrError();
- close(FLAGS_gatekeeper_fd_in);
-
- CHECK(FLAGS_gatekeeper_fd_out != -1);
- auto gatekeeper_out = cuttlefish::SharedFD::Dup(FLAGS_gatekeeper_fd_out);
- CHECK(gatekeeper_out->IsOpen()) << "Could not dup output fd: "
- << keymaster_out->StrError();
- close(FLAGS_gatekeeper_fd_out);
-
- std::thread keymaster_thread([keymaster_in, keymaster_out, &keymaster]() {
+ threads.emplace_back([keymaster_in, keymaster_out, &keymaster]() {
while (true) {
- cuttlefish::KeymasterChannel keymaster_channel(
- keymaster_in, keymaster_out);
+ KeymasterChannel keymaster_channel(keymaster_in, keymaster_out);
KeymasterResponder keymaster_responder(keymaster_channel, keymaster);
@@ -165,10 +243,9 @@
}
});
- std::thread gatekeeper_thread([gatekeeper_in, gatekeeper_out, &gatekeeper]() {
+ threads.emplace_back([gatekeeper_in, gatekeeper_out, &gatekeeper]() {
while (true) {
- cuttlefish::GatekeeperChannel gatekeeper_channel(
- gatekeeper_in, gatekeeper_out);
+ GatekeeperChannel gatekeeper_channel(gatekeeper_in, gatekeeper_out);
GatekeeperResponder gatekeeper_responder(gatekeeper_channel, *gatekeeper);
@@ -177,6 +254,22 @@
}
});
- keymaster_thread.join();
- gatekeeper_thread.join();
+ threads.emplace_back([confui_server_fd, resource_manager]() {
+ ConfUiSignServer confui_sign_server(*resource_manager, confui_server_fd);
+ // no return, infinite loop
+ confui_sign_server.MainLoop();
+ });
+ threads.emplace_back(StartKernelEventMonitor(kernel_events_fd));
+
+ for (auto& t : threads) {
+ t.join();
+ }
+
+ return 0;
+}
+
+} // namespace cuttlefish
+
+int main(int argc, char** argv) {
+ return cuttlefish::SecureEnvMain(argc, argv);
}
diff --git a/host/commands/secure_env/test_tpm.cpp b/host/commands/secure_env/test_tpm.cpp
new file mode 100644
index 0000000..14e43be
--- /dev/null
+++ b/host/commands/secure_env/test_tpm.cpp
@@ -0,0 +1,36 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "host/commands/secure_env/test_tpm.h"
+
+#include <android-base/logging.h>
+#include <tss2/tss2_rc.h>
+
+namespace cuttlefish {
+
+TestTpm::TestTpm() {
+ auto rc = Esys_Initialize(&esys_, tpm_.TctiContext(), nullptr);
+ if (rc != TPM2_RC_SUCCESS) {
+ LOG(FATAL) << "Could not initialize esys: " << Tss2_RC_Decode(rc) << " ("
+ << rc << ")";
+ }
+}
+
+TestTpm::~TestTpm() { Esys_Finalize(&esys_); }
+
+ESYS_CONTEXT* TestTpm::Esys() { return esys_; }
+
+} // namespace cuttlefish
diff --git a/common/libs/utils/size_utils.cpp b/host/commands/secure_env/test_tpm.h
similarity index 69%
copy from common/libs/utils/size_utils.cpp
copy to host/commands/secure_env/test_tpm.h
index 9f25445..fb39680 100644
--- a/common/libs/utils/size_utils.cpp
+++ b/host/commands/secure_env/test_tpm.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2018 The Android Open Source Project
+ * Copyright (C) 2021 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -14,15 +14,22 @@
* limitations under the License.
*/
-#include "common/libs/utils/size_utils.h"
+#include <tss2/tss2_esys.h>
-#include <unistd.h>
+#include "host/commands/secure_env/in_process_tpm.h"
namespace cuttlefish {
-uint64_t AlignToPowerOf2(uint64_t val, uint8_t align_log) {
- uint64_t align = 1ULL << align_log;
- return ((val + (align - 1)) / align) * align;
-}
+class TestTpm {
+ public:
+ TestTpm();
+ ~TestTpm();
+
+ ESYS_CONTEXT* Esys();
+
+ private:
+ InProcessTpm tpm_;
+ ESYS_CONTEXT* esys_;
+};
} // namespace cuttlefish
diff --git a/host/commands/secure_env/tpm.h b/host/commands/secure_env/tpm.h
index 201e5e7..3175ca7 100644
--- a/host/commands/secure_env/tpm.h
+++ b/host/commands/secure_env/tpm.h
@@ -17,9 +17,13 @@
#include <tss2/tss2_tcti.h>
+namespace cuttlefish {
+
class Tpm {
public:
virtual ~Tpm() = default;
virtual TSS2_TCTI_CONTEXT* TctiContext() = 0;
};
+
+} // namespace cuttlefish
diff --git a/host/commands/secure_env/tpm_attestation_record.cpp b/host/commands/secure_env/tpm_attestation_record.cpp
index 75a2920..674570b 100644
--- a/host/commands/secure_env/tpm_attestation_record.cpp
+++ b/host/commands/secure_env/tpm_attestation_record.cpp
@@ -22,10 +22,30 @@
#include <android-base/logging.h>
+namespace cuttlefish {
+
+namespace {
+using VerifiedBootParams = keymaster::AttestationContext::VerifiedBootParams;
using keymaster::AuthorizationSet;
+VerifiedBootParams MakeVbParams() {
+ VerifiedBootParams vb_params;
+
+ // TODO: If Cuttlefish ever supports a boot state other than "orange", we'll
+ // also need to plumb in the public key.
+ static uint8_t empty_vb_key[32] = {};
+ vb_params.verified_boot_key = {empty_vb_key, sizeof(empty_vb_key)};
+ vb_params.verified_boot_hash = {empty_vb_key, sizeof(empty_vb_key)};
+ vb_params.verified_boot_state = KM_VERIFIED_BOOT_UNVERIFIED;
+ vb_params.device_locked = false;
+ return vb_params;
+}
+
+} // namespace
+
TpmAttestationRecordContext::TpmAttestationRecordContext()
- : keymaster::AttestationContext(::keymaster::KmVersion::KEYMINT_1),
+ : keymaster::AttestationContext(::keymaster::KmVersion::KEYMINT_2),
+ vb_params_(MakeVbParams()),
unique_id_hbk_(16) {
RAND_bytes(unique_id_hbk_.data(), unique_id_hbk_.size());
}
@@ -35,15 +55,10 @@
}
keymaster_error_t TpmAttestationRecordContext::VerifyAndCopyDeviceIds(
- const AuthorizationSet& attestation_params,
- AuthorizationSet* attestation) const {
+ const AuthorizationSet& /*attestation_params*/,
+ AuthorizationSet* /*attestation*/) const {
LOG(DEBUG) << "TODO(schuffelen): Implement VerifyAndCopyDeviceIds";
- attestation->Difference(attestation_params);
- attestation->Union(attestation_params);
- if (int index = attestation->find(keymaster::TAG_ATTESTATION_APPLICATION_ID)) {
- attestation->erase(index);
- }
- return KM_ERROR_OK;
+ return KM_ERROR_UNIMPLEMENTED;
}
keymaster::Buffer TpmAttestationRecordContext::GenerateUniqueId(
@@ -54,28 +69,10 @@
application_id, reset_since_rotation);
}
-const keymaster::AttestationContext::VerifiedBootParams*
-TpmAttestationRecordContext::GetVerifiedBootParams(keymaster_error_t* error) const {
- LOG(DEBUG) << "TODO(schuffelen): Implement GetVerifiedBootParams";
- if (!vb_params_) {
- vb_params_.reset(new VerifiedBootParams{});
-
- // TODO(schuffelen): Get this data out of vbmeta
- static uint8_t fake_vb_key[32];
- static bool fake_vb_key_initialized = false;
- if (!fake_vb_key_initialized) {
- for (int i = 0; i < sizeof(fake_vb_key); i++) {
- fake_vb_key[i] = rand();
- }
- fake_vb_key_initialized = true;
- }
- vb_params_->verified_boot_key = {fake_vb_key, sizeof(fake_vb_key)};
- vb_params_->verified_boot_hash = {fake_vb_key, sizeof(fake_vb_key)};
- vb_params_->verified_boot_state = KM_VERIFIED_BOOT_VERIFIED;
- vb_params_->device_locked = true;
- }
+const VerifiedBootParams* TpmAttestationRecordContext::GetVerifiedBootParams(
+ keymaster_error_t* error) const {
*error = KM_ERROR_OK;
- return vb_params_.get();
+ return &vb_params_;
}
keymaster::KeymasterKeyBlob
@@ -89,3 +86,25 @@
keymaster_error_t* error) const {
return keymaster::getAttestationChain(algorithm, error);
}
+
+void TpmAttestationRecordContext::SetVerifiedBootInfo(
+ std::string_view verified_boot_state, std::string_view bootloader_state,
+ const std::vector<uint8_t>& vbmeta_digest) {
+ vbmeta_digest_ = vbmeta_digest;
+ vb_params_.verified_boot_hash = {vbmeta_digest_.data(),
+ vbmeta_digest_.size()};
+
+ if (verified_boot_state == "green") {
+ vb_params_.verified_boot_state = KM_VERIFIED_BOOT_VERIFIED;
+ } else if (verified_boot_state == "yellow") {
+ vb_params_.verified_boot_state = KM_VERIFIED_BOOT_SELF_SIGNED;
+ } else if (verified_boot_state == "red") {
+ vb_params_.verified_boot_state = KM_VERIFIED_BOOT_FAILED;
+ } else { // Default to orange
+ vb_params_.verified_boot_state = KM_VERIFIED_BOOT_UNVERIFIED;
+ }
+
+ vb_params_.device_locked = bootloader_state == "locked";
+}
+
+} // namespace cuttlefish
diff --git a/host/commands/secure_env/tpm_attestation_record.h b/host/commands/secure_env/tpm_attestation_record.h
index 1609351..dba0e91 100644
--- a/host/commands/secure_env/tpm_attestation_record.h
+++ b/host/commands/secure_env/tpm_attestation_record.h
@@ -15,11 +15,17 @@
#pragma once
+#include <cstdint>
#include <memory>
+#include <optional>
+#include <string>
+#include <string_view>
#include <vector>
#include <keymaster/attestation_context.h>
+namespace cuttlefish {
+
class TpmAttestationRecordContext : public keymaster::AttestationContext {
public:
TpmAttestationRecordContext();
@@ -37,8 +43,14 @@
keymaster_algorithm_t algorithm, keymaster_error_t* error) const override;
keymaster::CertificateChain GetAttestationChain(
keymaster_algorithm_t algorithm, keymaster_error_t* error) const override;
+ void SetVerifiedBootInfo(std::string_view verified_boot_state,
+ std::string_view bootloader_state,
+ const std::vector<uint8_t>& vbmeta_digest);
private:
- mutable std::unique_ptr<VerifiedBootParams> vb_params_;
- std::vector<uint8_t> unique_id_hbk_;
+ std::vector<uint8_t> vbmeta_digest_;
+ VerifiedBootParams vb_params_;
+ std::vector<uint8_t> unique_id_hbk_;
};
+
+} // namespace cuttlefish
diff --git a/host/commands/secure_env/tpm_auth.cpp b/host/commands/secure_env/tpm_auth.cpp
index 10f4b2d..937dd5d 100644
--- a/host/commands/secure_env/tpm_auth.cpp
+++ b/host/commands/secure_env/tpm_auth.cpp
@@ -17,6 +17,8 @@
#include <tuple>
+namespace cuttlefish {
+
TpmAuth::TpmAuth(ESYS_TR auth): TpmAuth(auth, ESYS_TR_NONE, ESYS_TR_NONE) {}
TpmAuth::TpmAuth(ESYS_TR auth1, ESYS_TR auth2)
: TpmAuth(auth1, auth2, ESYS_TR_NONE) {}
@@ -41,3 +43,5 @@
ESYS_TR TpmAuth::auth3() const {
return auth3_;
}
+
+} // namespace cuttlefish
diff --git a/host/commands/secure_env/tpm_auth.h b/host/commands/secure_env/tpm_auth.h
index 196b5fd..dec75c4 100644
--- a/host/commands/secure_env/tpm_auth.h
+++ b/host/commands/secure_env/tpm_auth.h
@@ -17,6 +17,8 @@
#include <tss2/tss2_esys.h>
+namespace cuttlefish {
+
/**
* Authorization wrapper for TPM2 calls.
*
@@ -41,3 +43,5 @@
ESYS_TR auth2_;
ESYS_TR auth3_;
};
+
+} // namespace cuttlefish
diff --git a/host/commands/secure_env/tpm_commands.cpp b/host/commands/secure_env/tpm_commands.cpp
index 78e9d7f..932202a 100644
--- a/host/commands/secure_env/tpm_commands.cpp
+++ b/host/commands/secure_env/tpm_commands.cpp
@@ -21,6 +21,8 @@
#include <cstddef>
#include <string>
+namespace cuttlefish {
+
std::string TpmCommandName(std::uint32_t command_num) {
switch(command_num) {
#define MATCH_TPM_COMMAND(name) case name: return #name;
@@ -145,3 +147,5 @@
return "Unknown";
}
}
+
+} // namespace cuttlefish
diff --git a/host/commands/secure_env/tpm_commands.h b/host/commands/secure_env/tpm_commands.h
index ee75844..421aa51 100644
--- a/host/commands/secure_env/tpm_commands.h
+++ b/host/commands/secure_env/tpm_commands.h
@@ -19,4 +19,8 @@
#include <cstddef>
#include <string>
+namespace cuttlefish {
+
std::string TpmCommandName(std::uint32_t command_num);
+
+}
diff --git a/host/commands/secure_env/tpm_encrypt_decrypt.cpp b/host/commands/secure_env/tpm_encrypt_decrypt.cpp
index 4a1711e..c81d7d6 100644
--- a/host/commands/secure_env/tpm_encrypt_decrypt.cpp
+++ b/host/commands/secure_env/tpm_encrypt_decrypt.cpp
@@ -22,23 +22,23 @@
#include <android-base/logging.h>
#include <tss2/tss2_rc.h>
+namespace cuttlefish {
+
using keymaster::KeymasterBlob;
-static bool TpmEncryptDecrypt(
- ESYS_CONTEXT* esys,
- ESYS_TR key_handle,
- TpmAuth auth,
- uint8_t* data_in,
- uint8_t* data_out,
- size_t data_size,
- bool decrypt) {
+static bool TpmEncryptDecrypt( //
+ ESYS_CONTEXT* esys, ESYS_TR key_handle, TpmAuth auth, const TPM2B_IV& iv,
+ uint8_t* data_in, uint8_t* data_out, size_t data_size, bool decrypt) {
+ if (iv.size != sizeof(iv.buffer)) {
+ LOG(ERROR) << "Input IV had wrong size: " << iv.size;
+ return false;
+ }
// TODO(schuffelen): Pipeline this for performance. Will require reevaluating
// the initialization vector logic.
std::vector<unsigned char> converted(data_size);
// malloc for parity with Esys_EncryptDecrypt2
TPM2B_IV* init_vector_in = (TPM2B_IV*) malloc(sizeof(TPM2B_IV));
- *init_vector_in = {};
- init_vector_in->size = 16;
+ *init_vector_in = iv;
for (auto processed = 0; processed < data_size;) {
TPM2B_MAX_BUFFER in_data;
in_data.size =
@@ -77,24 +77,18 @@
return true;
}
-bool TpmEncrypt(
- ESYS_CONTEXT* esys,
- ESYS_TR key_handle,
- TpmAuth auth,
- uint8_t* data_in,
- uint8_t* data_out,
- size_t data_size) {
- return TpmEncryptDecrypt(
- esys, key_handle, auth, data_in, data_out, data_size, false);
+bool TpmEncrypt(ESYS_CONTEXT* esys, ESYS_TR key_handle, TpmAuth auth,
+ const TPM2B_IV& iv, uint8_t* data_in, uint8_t* data_out,
+ size_t data_size) {
+ return TpmEncryptDecrypt( //
+ esys, key_handle, auth, iv, data_in, data_out, data_size, false);
}
-bool TpmDecrypt(
- ESYS_CONTEXT* esys,
- ESYS_TR key_handle,
- TpmAuth auth,
- uint8_t* data_in,
- uint8_t* data_out,
- size_t data_size) {
- return TpmEncryptDecrypt(
- esys, key_handle, auth, data_in, data_out, data_size, true);
+bool TpmDecrypt(ESYS_CONTEXT* esys, ESYS_TR key_handle, TpmAuth auth,
+ const TPM2B_IV& iv, uint8_t* data_in, uint8_t* data_out,
+ size_t data_size) {
+ return TpmEncryptDecrypt( //
+ esys, key_handle, auth, iv, data_in, data_out, data_size, true);
}
+
+} // namespace cuttlefish
diff --git a/host/commands/secure_env/tpm_encrypt_decrypt.h b/host/commands/secure_env/tpm_encrypt_decrypt.h
index b75bafb..a811cb7 100644
--- a/host/commands/secure_env/tpm_encrypt_decrypt.h
+++ b/host/commands/secure_env/tpm_encrypt_decrypt.h
@@ -20,20 +20,20 @@
#include "host/commands/secure_env/tpm_auth.h"
+namespace cuttlefish {
+
/**
* Encrypt `data_in` to `data_out`, which are both buffers of size `data_size`.
*
* There are no integrity guarantees on this data: if the encrypted data is
* corrupted, decrypting it could either fail or produce corrupted output.
+ *
+ * `iv` should be generated randomly, and can be stored unencrypted next to
+ * the plaintext.
*/
-bool TpmEncrypt(
- ESYS_CONTEXT* esys,
- ESYS_TR key_handle,
- TpmAuth auth,
- uint8_t* data_in,
- uint8_t* data_out,
- size_t data_size);
-
+bool TpmEncrypt(ESYS_CONTEXT* esys, ESYS_TR key_handle, TpmAuth auth,
+ const TPM2B_IV& iv, uint8_t* data_in, uint8_t* data_out,
+ size_t data_size);
/**
* Decrypt `data_in` to `data_out`, which are both buffers of size `data_size`.
@@ -41,10 +41,8 @@
* There are no integrity guarantees on this data: if the encrypted data is
* corrupted, decrypting it could either fail or produce corrupted output.
*/
-bool TpmDecrypt(
- ESYS_CONTEXT* esys,
- ESYS_TR key_handle,
- TpmAuth auth,
- uint8_t* data_in,
- uint8_t* data_out,
- size_t data_size);
+bool TpmDecrypt(ESYS_CONTEXT* esys, ESYS_TR key_handle, TpmAuth auth,
+ const TPM2B_IV& iv, uint8_t* data_in, uint8_t* data_out,
+ size_t data_size);
+
+} // namespace cuttlefish
diff --git a/host/commands/secure_env/tpm_gatekeeper.cpp b/host/commands/secure_env/tpm_gatekeeper.cpp
index e4f61e7..c8497d5 100644
--- a/host/commands/secure_env/tpm_gatekeeper.cpp
+++ b/host/commands/secure_env/tpm_gatekeeper.cpp
@@ -29,6 +29,8 @@
#include "host/commands/secure_env/tpm_hmac.h"
#include "host/commands/secure_env/tpm_random_source.h"
+namespace cuttlefish {
+
TpmGatekeeper::TpmGatekeeper(
TpmResourceManager& resource_manager,
GatekeeperStorage& secure_storage,
@@ -240,3 +242,4 @@
return true;
}
+} // namespace cuttlefish
diff --git a/host/commands/secure_env/tpm_gatekeeper.h b/host/commands/secure_env/tpm_gatekeeper.h
index 021ab58..1c35bdc 100644
--- a/host/commands/secure_env/tpm_gatekeeper.h
+++ b/host/commands/secure_env/tpm_gatekeeper.h
@@ -21,6 +21,8 @@
#include "host/commands/secure_env/gatekeeper_storage.h"
#include "host/commands/secure_env/tpm_resource_manager.h"
+namespace cuttlefish {
+
/**
* See method descriptions for this class in
* system/gatekeeper/include/gatekeeper/gatekeeper.h
@@ -81,3 +83,5 @@
GatekeeperStorage& secure_storage_;
GatekeeperStorage& insecure_storage_;
};
+
+} // namespace cuttlefish
diff --git a/host/commands/secure_env/tpm_hmac.cpp b/host/commands/secure_env/tpm_hmac.cpp
index 80003d0..b566659 100644
--- a/host/commands/secure_env/tpm_hmac.cpp
+++ b/host/commands/secure_env/tpm_hmac.cpp
@@ -20,6 +20,8 @@
#include "host/commands/secure_env/tpm_resource_manager.h"
+namespace cuttlefish {
+
/* For data large enough to fit in a single TPM2_HMAC call. */
static UniqueEsysPtr<TPM2B_DIGEST> OneshotHmac(
TpmResourceManager& resource_manager,
@@ -153,3 +155,5 @@
auto fn = data_size > TPM2_MAX_DIGEST_BUFFER ? SegmentedHmac : OneshotHmac;
return fn(resource_manager, key_handle, auth, data, data_size);
}
+
+} // namespace cuttlefish
diff --git a/host/commands/secure_env/tpm_hmac.h b/host/commands/secure_env/tpm_hmac.h
index e4686b6..09aa011 100644
--- a/host/commands/secure_env/tpm_hmac.h
+++ b/host/commands/secure_env/tpm_hmac.h
@@ -21,6 +21,8 @@
#include "host/commands/secure_env/tpm_auth.h"
+namespace cuttlefish {
+
class TpmResourceManager;
struct EsysDeleter {
@@ -49,3 +51,5 @@
TpmAuth auth,
const uint8_t* data,
size_t data_size);
+
+} // namespace cuttlefish
diff --git a/host/commands/secure_env/tpm_key_blob_maker.cpp b/host/commands/secure_env/tpm_key_blob_maker.cpp
index 64f344a..38236d8 100644
--- a/host/commands/secure_env/tpm_key_blob_maker.cpp
+++ b/host/commands/secure_env/tpm_key_blob_maker.cpp
@@ -26,6 +26,8 @@
#include "host/commands/secure_env/hmac_serializable.h"
#include "host/commands/secure_env/primary_key_builder.h"
+namespace cuttlefish {
+
using keymaster::AuthorizationSet;
using keymaster::KeymasterKeyBlob;
using keymaster::Serializable;
@@ -41,9 +43,76 @@
static keymaster_error_t SplitEnforcedProperties(
const keymaster::AuthorizationSet& key_description,
keymaster::AuthorizationSet* hw_enforced,
- keymaster::AuthorizationSet* sw_enforced) {
+ keymaster::AuthorizationSet* sw_enforced,
+ keymaster::AuthorizationSet* hidden) {
for (auto& entry : key_description) {
switch (entry.tag) {
+ // These cannot be specified by the client.
+ case KM_TAG_BOOT_PATCHLEVEL:
+ case KM_TAG_ORIGIN:
+ case KM_TAG_OS_PATCHLEVEL:
+ case KM_TAG_OS_VERSION:
+ case KM_TAG_ROOT_OF_TRUST:
+ case KM_TAG_VENDOR_PATCHLEVEL:
+ LOG(DEBUG) << "Root of trust and origin tags may not be specified";
+ return KM_ERROR_INVALID_TAG;
+
+ // These are hidden
+ case KM_TAG_APPLICATION_DATA:
+ case KM_TAG_APPLICATION_ID:
+ hidden->push_back(entry);
+ break;
+
+ // These should not be in key descriptions because they're for operation
+ // parameters.
+ case KM_TAG_ASSOCIATED_DATA:
+ case KM_TAG_AUTH_TOKEN:
+ case KM_TAG_CONFIRMATION_TOKEN:
+ case KM_TAG_INVALID:
+ case KM_TAG_MAC_LENGTH:
+ case KM_TAG_NONCE:
+ LOG(DEBUG) << "Tag " << entry.tag
+ << " not allowed in key generation/import";
+ break;
+
+ // These are provided to support attestation key generation, but should
+ // not be included in the key characteristics.
+ case KM_TAG_ATTESTATION_APPLICATION_ID:
+ case KM_TAG_ATTESTATION_CHALLENGE:
+ case KM_TAG_ATTESTATION_ID_BRAND:
+ case KM_TAG_ATTESTATION_ID_DEVICE:
+ case KM_TAG_ATTESTATION_ID_IMEI:
+ case KM_TAG_ATTESTATION_ID_MANUFACTURER:
+ case KM_TAG_ATTESTATION_ID_MEID:
+ case KM_TAG_ATTESTATION_ID_MODEL:
+ case KM_TAG_ATTESTATION_ID_PRODUCT:
+ case KM_TAG_ATTESTATION_ID_SERIAL:
+ case KM_TAG_CERTIFICATE_SERIAL:
+ case KM_TAG_CERTIFICATE_SUBJECT:
+ case KM_TAG_CERTIFICATE_NOT_BEFORE:
+ case KM_TAG_CERTIFICATE_NOT_AFTER:
+ case KM_TAG_RESET_SINCE_ID_ROTATION:
+ break;
+
+ // strongbox-only tags
+ case KM_TAG_DEVICE_UNIQUE_ATTESTATION:
+ LOG(DEBUG) << "Strongbox-only tag: " << entry.tag;
+ return KM_ERROR_UNSUPPORTED_TAG;
+
+ case KM_TAG_ROLLBACK_RESISTANT:
+ return KM_ERROR_UNSUPPORTED_TAG;
+
+ case KM_TAG_ROLLBACK_RESISTANCE:
+ LOG(DEBUG) << "Rollback resistance is not implemented.";
+ return KM_ERROR_ROLLBACK_RESISTANCE_UNAVAILABLE;
+
+ // These are nominally HW tags, but we don't actually support HW key
+ // attestation yet.
+ case KM_TAG_ALLOW_WHILE_ON_BODY:
+ case KM_TAG_EXPORTABLE:
+ case KM_TAG_IDENTITY_CREDENTIAL_KEY:
+ case KM_TAG_STORAGE_KEY:
+
case KM_TAG_PURPOSE:
case KM_TAG_ALGORITHM:
case KM_TAG_KEY_SIZE:
@@ -63,17 +132,32 @@
case KM_TAG_EC_CURVE:
case KM_TAG_ECIES_SINGLE_HASH_MODE:
case KM_TAG_USER_AUTH_TYPE:
- case KM_TAG_ORIGIN:
- case KM_TAG_OS_VERSION:
- case KM_TAG_OS_PATCHLEVEL:
case KM_TAG_EARLY_BOOT_ONLY:
case KM_TAG_UNLOCKED_DEVICE_REQUIRED:
hw_enforced->push_back(entry);
break;
- default:
+
+ // The remaining tags are all software.
+ case KM_TAG_ACTIVE_DATETIME:
+ case KM_TAG_ALL_APPLICATIONS:
+ case KM_TAG_ALL_USERS:
+ case KM_TAG_BOOTLOADER_ONLY:
+ case KM_TAG_CREATION_DATETIME:
+ case KM_TAG_INCLUDE_UNIQUE_ID:
+ case KM_TAG_MAX_BOOT_LEVEL:
+ case KM_TAG_ORIGINATION_EXPIRE_DATETIME:
+ case KM_TAG_RSA_OAEP_MGF_DIGEST:
+ case KM_TAG_TRUSTED_CONFIRMATION_REQUIRED:
+ case KM_TAG_TRUSTED_USER_PRESENCE_REQUIRED:
+ case KM_TAG_UNIQUE_ID:
+ case KM_TAG_USAGE_COUNT_LIMIT:
+ case KM_TAG_USAGE_EXPIRE_DATETIME:
+ case KM_TAG_USER_ID:
sw_enforced->push_back(entry);
+ break;
}
}
+
return KM_ERROR_OK;
}
@@ -102,20 +186,9 @@
KeymasterKeyBlob* blob,
AuthorizationSet* hw_enforced,
AuthorizationSet* sw_enforced) const {
- std::set<keymaster_tag_t> protected_tags = {
- KM_TAG_ROOT_OF_TRUST,
- KM_TAG_ORIGIN,
- KM_TAG_OS_VERSION,
- KM_TAG_OS_PATCHLEVEL,
- };
- for (auto tag : protected_tags) {
- if (key_description.Contains(tag)) {
- LOG(ERROR) << "Invalid tag " << tag;
- return KM_ERROR_INVALID_TAG;
- }
- }
- auto rc =
- SplitEnforcedProperties(key_description, hw_enforced, sw_enforced);
+ AuthorizationSet hidden;
+ auto rc = SplitEnforcedProperties(key_description, hw_enforced, sw_enforced,
+ &hidden);
if (rc != KM_ERROR_OK) {
return rc;
}
@@ -125,13 +198,22 @@
hw_enforced->push_back(keymaster::TAG_OS_VERSION, os_version_);
hw_enforced->push_back(keymaster::TAG_OS_PATCHLEVEL, os_patchlevel_);
+ if (vendor_patchlevel_) {
+ hw_enforced->push_back(keymaster::TAG_VENDOR_PATCHLEVEL,
+ *vendor_patchlevel_);
+ }
+ if (boot_patchlevel_) {
+ hw_enforced->push_back(keymaster::TAG_BOOT_PATCHLEVEL, *boot_patchlevel_);
+ }
+
return UnvalidatedCreateKeyBlob(key_material, *hw_enforced, *sw_enforced,
- blob);
+ hidden, blob);
}
keymaster_error_t TpmKeyBlobMaker::UnvalidatedCreateKeyBlob(
const KeymasterKeyBlob& key_material, const AuthorizationSet& hw_enforced,
- const AuthorizationSet& sw_enforced, KeymasterKeyBlob* blob) const {
+ const AuthorizationSet& sw_enforced, const AuthorizationSet& hidden,
+ KeymasterKeyBlob* blob) const {
keymaster::Buffer key_material_buffer(
key_material.key_material, key_material.key_material_size);
AuthorizationSet hw_enforced_mutable = hw_enforced;
@@ -142,8 +224,12 @@
EncryptedSerializable encryption(
resource_manager_, parent_key_fn, sensitive_material);
auto signing_key_fn = SigningKeyCreator(kUniqueKey);
- HmacSerializable sign_check(
- resource_manager_, signing_key_fn, TPM2_SHA256_DIGEST_SIZE, &encryption);
+ // TODO(b/154956668) The "hidden" tags should also be mixed into the TPM ACL
+ // so that the TPM requires them to be presented to unwrap the key. This is
+ // necessary to meet the requirement that full breach of KeyMint means an
+ // attacker cannot unwrap keys w/o the application id/data.
+ HmacSerializable sign_check(resource_manager_, signing_key_fn,
+ TPM2_SHA256_DIGEST_SIZE, &encryption, &hidden);
auto generated_blob = SerializableToKeyBlob(sign_check);
LOG(VERBOSE) << "Keymaster key size: " << generated_blob.key_material_size;
if (generated_blob.key_material_size != 0) {
@@ -155,9 +241,8 @@
}
keymaster_error_t TpmKeyBlobMaker::UnwrapKeyBlob(
- const keymaster_key_blob_t& blob,
- AuthorizationSet* hw_enforced,
- AuthorizationSet* sw_enforced,
+ const keymaster_key_blob_t& blob, AuthorizationSet* hw_enforced,
+ AuthorizationSet* sw_enforced, const AuthorizationSet& hidden,
KeymasterKeyBlob* key_material) const {
keymaster::Buffer key_material_buffer(blob.key_material_size);
CompositeSerializable sensitive_material(
@@ -166,17 +251,17 @@
EncryptedSerializable encryption(
resource_manager_, parent_key_fn, sensitive_material);
auto signing_key_fn = SigningKeyCreator(kUniqueKey);
- HmacSerializable sign_check(
- resource_manager_, signing_key_fn, TPM2_SHA256_DIGEST_SIZE, &encryption);
+ HmacSerializable sign_check(resource_manager_, signing_key_fn,
+ TPM2_SHA256_DIGEST_SIZE, &encryption, &hidden);
auto buf = blob.key_material;
auto buf_end = buf + blob.key_material_size;
if (!sign_check.Deserialize(&buf, buf_end)) {
LOG(ERROR) << "Failed to deserialize key.";
- return KM_ERROR_UNKNOWN_ERROR;
+ return KM_ERROR_INVALID_KEY_BLOB;
}
if (key_material_buffer.available_read() == 0) {
LOG(ERROR) << "Key material was corrupted and the size was too large";
- return KM_ERROR_UNKNOWN_ERROR;
+ return KM_ERROR_INVALID_KEY_BLOB;
}
*key_material = KeymasterKeyBlob(
key_material_buffer.peek_read(), key_material_buffer.available_read());
@@ -185,8 +270,22 @@
keymaster_error_t TpmKeyBlobMaker::SetSystemVersion(
uint32_t os_version, uint32_t os_patchlevel) {
- // TODO(b/155697375): Only accept new values of these from the bootloader
+ // TODO(b/201561154): Only accept new values of these from the bootloader
os_version_ = os_version;
os_patchlevel_ = os_patchlevel;
return KM_ERROR_OK;
}
+
+keymaster_error_t TpmKeyBlobMaker::SetVendorPatchlevel(uint32_t patchlevel) {
+ // TODO(b/201561154): Only accept new values of these from the bootloader
+ vendor_patchlevel_ = patchlevel;
+ return KM_ERROR_OK;
+}
+
+keymaster_error_t TpmKeyBlobMaker::SetBootPatchlevel(uint32_t boot_patchlevel) {
+ // TODO(b/201561154): Only accept new values of these from the bootloader
+ boot_patchlevel_ = boot_patchlevel;
+ return KM_ERROR_OK;
+}
+
+} // namespace cuttlefish
diff --git a/host/commands/secure_env/tpm_key_blob_maker.h b/host/commands/secure_env/tpm_key_blob_maker.h
index a0483b4..b76d1dc 100644
--- a/host/commands/secure_env/tpm_key_blob_maker.h
+++ b/host/commands/secure_env/tpm_key_blob_maker.h
@@ -15,10 +15,14 @@
#pragma once
+#include <optional>
+//
#include <keymaster/soft_key_factory.h>
-
+//
#include "host/commands/secure_env/tpm_resource_manager.h"
+namespace cuttlefish {
+
/**
* Encrypts key data using a TPM-resident key and signs it with a TPM-resident
* key for privacy and integrity.
@@ -43,6 +47,7 @@
const keymaster::KeymasterKeyBlob& key_material,
const keymaster::AuthorizationSet& hw_enforced,
const keymaster::AuthorizationSet& sw_enforced,
+ const keymaster::AuthorizationSet& hidden,
keymaster::KeymasterKeyBlob* blob) const;
/**
@@ -60,11 +65,19 @@
const keymaster_key_blob_t& blob,
keymaster::AuthorizationSet* hw_enforced,
keymaster::AuthorizationSet* sw_enforced,
+ const keymaster::AuthorizationSet& hidden,
keymaster::KeymasterKeyBlob* key_material) const;
keymaster_error_t SetSystemVersion(uint32_t os_version, uint32_t os_patchlevel);
-private:
+ keymaster_error_t SetVendorPatchlevel(uint32_t vendor_patchlevel);
+ keymaster_error_t SetBootPatchlevel(uint32_t boot_patchlevel);
+
+ private:
TpmResourceManager& resource_manager_;
uint32_t os_version_;
uint32_t os_patchlevel_;
+ std::optional<uint32_t> vendor_patchlevel_;
+ std::optional<uint32_t> boot_patchlevel_;
};
+
+} // namespace cuttlefish
diff --git a/host/commands/secure_env/tpm_keymaster_context.cpp b/host/commands/secure_env/tpm_keymaster_context.cpp
index 850d48b..4a60915 100644
--- a/host/commands/secure_env/tpm_keymaster_context.cpp
+++ b/host/commands/secure_env/tpm_keymaster_context.cpp
@@ -26,28 +26,66 @@
#include <keymaster/km_openssl/rsa_key_factory.h>
#include <keymaster/km_openssl/soft_keymaster_enforcement.h>
#include <keymaster/km_openssl/triple_des_key.h>
+#include <keymaster/operation.h>
+#include <keymaster/wrapped_key.h>
#include "host/commands/secure_env/tpm_attestation_record.h"
-#include "host/commands/secure_env/tpm_random_source.h"
#include "host/commands/secure_env/tpm_key_blob_maker.h"
+#include "host/commands/secure_env/tpm_random_source.h"
+#include "host/commands/secure_env/tpm_remote_provisioning_context.h"
+namespace cuttlefish {
+
+namespace {
using keymaster::AuthorizationSet;
-using keymaster::KeymasterKeyBlob;
using keymaster::KeyFactory;
+using keymaster::KeymasterBlob;
+using keymaster::KeymasterKeyBlob;
using keymaster::OperationFactory;
+keymaster::AuthorizationSet GetHiddenTags(
+ const AuthorizationSet& authorizations) {
+ keymaster::AuthorizationSet output;
+ keymaster_blob_t entry;
+ if (authorizations.GetTagValue(keymaster::TAG_APPLICATION_ID, &entry)) {
+ output.push_back(keymaster::TAG_APPLICATION_ID, entry.data,
+ entry.data_length);
+ }
+ if (authorizations.GetTagValue(keymaster::TAG_APPLICATION_DATA, &entry)) {
+ output.push_back(keymaster::TAG_APPLICATION_DATA, entry.data,
+ entry.data_length);
+ }
+ return output;
+}
+
+keymaster_error_t TranslateAuthorizationSetError(AuthorizationSet::Error err) {
+ switch (err) {
+ case AuthorizationSet::OK:
+ return KM_ERROR_OK;
+ case AuthorizationSet::ALLOCATION_FAILURE:
+ return KM_ERROR_MEMORY_ALLOCATION_FAILED;
+ case AuthorizationSet::MALFORMED_DATA:
+ return KM_ERROR_UNKNOWN_ERROR;
+ }
+ return KM_ERROR_UNKNOWN_ERROR;
+}
+
+} // namespace
+
TpmKeymasterContext::TpmKeymasterContext(
TpmResourceManager& resource_manager,
keymaster::KeymasterEnforcement& enforcement)
- : resource_manager_(resource_manager)
- , enforcement_(enforcement)
- , key_blob_maker_(new TpmKeyBlobMaker(resource_manager_))
- , random_source_(new TpmRandomSource(resource_manager_.Esys()))
- , attestation_context_(new TpmAttestationRecordContext()) {
- key_factories_.emplace(
- KM_ALGORITHM_RSA, new keymaster::RsaKeyFactory(*key_blob_maker_, *this));
- key_factories_.emplace(
- KM_ALGORITHM_EC, new keymaster::EcKeyFactory(*key_blob_maker_, *this));
+ : resource_manager_(resource_manager),
+ enforcement_(enforcement),
+ key_blob_maker_(new TpmKeyBlobMaker(resource_manager_)),
+ random_source_(new TpmRandomSource(resource_manager_.Esys())),
+ attestation_context_(new TpmAttestationRecordContext),
+ remote_provisioning_context_(
+ new TpmRemoteProvisioningContext(resource_manager_)) {
+ key_factories_.emplace(KM_ALGORITHM_RSA,
+ new keymaster::RsaKeyFactory(*key_blob_maker_, *this));
+ key_factories_.emplace(KM_ALGORITHM_EC,
+ new keymaster::EcKeyFactory(*key_blob_maker_, *this));
key_factories_.emplace(
KM_ALGORITHM_AES,
new keymaster::AesKeyFactory(*key_blob_maker_, *random_source_));
@@ -68,11 +106,12 @@
os_version_ = os_version;
os_patchlevel_ = os_patchlevel;
key_blob_maker_->SetSystemVersion(os_version, os_patchlevel);
+ remote_provisioning_context_->SetSystemVersion(os_version_, os_patchlevel_);
return KM_ERROR_OK;
}
-void TpmKeymasterContext::GetSystemVersion(
- uint32_t* os_version, uint32_t* os_patchlevel) const {
+void TpmKeymasterContext::GetSystemVersion(uint32_t* os_version,
+ uint32_t* os_patchlevel) const {
*os_version = os_version_;
*os_patchlevel = os_patchlevel_;
}
@@ -87,12 +126,12 @@
return it->second.get();
}
-const OperationFactory* TpmKeymasterContext::GetOperationFactory(
+OperationFactory* TpmKeymasterContext::GetOperationFactory(
keymaster_algorithm_t algorithm, keymaster_purpose_t purpose) const {
auto key_factory = GetKeyFactory(algorithm);
if (key_factory == nullptr) {
LOG(ERROR) << "Tried to get operation factory for " << purpose
- << " for invalid algorithm " << algorithm;
+ << " for invalid algorithm " << algorithm;
return nullptr;
}
auto operation_factory = key_factory->GetOperationFactory(purpose);
@@ -104,18 +143,16 @@
}
const keymaster_algorithm_t* TpmKeymasterContext::GetSupportedAlgorithms(
- size_t* algorithms_count) const {
+ size_t* algorithms_count) const {
*algorithms_count = supported_algorithms_.size();
return supported_algorithms_.data();
}
-// Based on https://cs.android.com/android/platform/superproject/+/master:system/keymaster/key_blob_utils/software_keyblobs.cpp;l=44;drc=master
+// Based on
+// https://cs.android.com/android/platform/superproject/+/master:system/keymaster/key_blob_utils/software_keyblobs.cpp;l=44;drc=master
-static bool UpgradeIntegerTag(
- keymaster_tag_t tag,
- uint32_t value,
- AuthorizationSet* set,
- bool* set_changed) {
+static bool UpgradeIntegerTag(keymaster_tag_t tag, uint32_t value,
+ AuthorizationSet* set, bool* set_changed) {
int index = set->find(tag);
if (index == -1) {
keymaster_key_param_t param;
@@ -137,7 +174,8 @@
return true;
}
-// Based on https://cs.android.com/android/platform/superproject/+/master:system/keymaster/key_blob_utils/software_keyblobs.cpp;l=310;drc=master
+// Based on
+// https://cs.android.com/android/platform/superproject/+/master:system/keymaster/key_blob_utils/software_keyblobs.cpp;l=310;drc=master
keymaster_error_t TpmKeymasterContext::UpgradeKeyBlob(
const KeymasterKeyBlob& blob_to_upgrade,
@@ -187,23 +225,20 @@
return key_blob_maker_->UnvalidatedCreateKeyBlob(
key->key_material(), key->hw_enforced(), key->sw_enforced(),
- upgraded_key);
+ GetHiddenTags(upgrade_params), upgraded_key);
}
keymaster_error_t TpmKeymasterContext::ParseKeyBlob(
- const KeymasterKeyBlob& blob,
- const AuthorizationSet& additional_params,
+ const KeymasterKeyBlob& blob, const AuthorizationSet& additional_params,
keymaster::UniquePtr<keymaster::Key>* key) const {
keymaster::AuthorizationSet hw_enforced;
keymaster::AuthorizationSet sw_enforced;
keymaster::KeymasterKeyBlob key_material;
- auto rc =
- key_blob_maker_->UnwrapKeyBlob(
- blob,
- &hw_enforced,
- &sw_enforced,
- &key_material);
+ keymaster::AuthorizationSet hidden = GetHiddenTags(additional_params);
+
+ auto rc = key_blob_maker_->UnwrapKeyBlob(blob, &hw_enforced, &sw_enforced,
+ hidden, &key_material);
if (rc != KM_ERROR_OK) {
LOG(ERROR) << "Failed to unwrap key: " << rc;
return rc;
@@ -221,21 +256,16 @@
LOG(ERROR) << "Unable to find key factory for " << algorithm;
return KM_ERROR_UNSUPPORTED_ALGORITHM;
}
- rc =
- factory->LoadKey(
- std::move(key_material),
- additional_params,
- std::move(hw_enforced),
- std::move(sw_enforced),
- key);
+ rc = factory->LoadKey(std::move(key_material), additional_params,
+ std::move(hw_enforced), std::move(sw_enforced), key);
if (rc != KM_ERROR_OK) {
LOG(ERROR) << "Unable to load unwrapped key: " << rc;
}
return rc;
}
-keymaster_error_t TpmKeymasterContext::AddRngEntropy(
- const uint8_t* buffer, size_t size) const {
+keymaster_error_t TpmKeymasterContext::AddRngEntropy(const uint8_t* buffer,
+ size_t size) const {
return random_source_->AddRngEntropy(buffer, size);
}
@@ -243,22 +273,25 @@
return &enforcement_;
}
-// Based on https://cs.android.com/android/platform/superproject/+/master:system/keymaster/contexts/pure_soft_keymaster_context.cpp;l=261;drc=8367d5351c4d417a11f49b12394b63a413faa02d
+// Based on
+// https://cs.android.com/android/platform/superproject/+/master:system/keymaster/contexts/pure_soft_keymaster_context.cpp;l=261;drc=8367d5351c4d417a11f49b12394b63a413faa02d
keymaster::CertificateChain TpmKeymasterContext::GenerateAttestation(
const keymaster::Key& key, const keymaster::AuthorizationSet& attest_params,
- keymaster::UniquePtr<keymaster::Key> /* attest_key */,
- const keymaster::KeymasterBlob& /* issuer_subject */,
+ keymaster::UniquePtr<keymaster::Key> attest_key,
+ const keymaster::KeymasterBlob& issuer_subject,
keymaster_error_t* error) const {
LOG(INFO) << "TODO(b/155697200): Link attestation back to the TPM";
keymaster_algorithm_t key_algorithm;
if (!key.authorizations().GetTagValue(keymaster::TAG_ALGORITHM,
&key_algorithm)) {
+ LOG(ERROR) << "Cannot find key algorithm (TAG_ALGORITHM)";
*error = KM_ERROR_UNKNOWN_ERROR;
return {};
}
if ((key_algorithm != KM_ALGORITHM_RSA && key_algorithm != KM_ALGORITHM_EC)) {
+ LOG(ERROR) << "Invalid algorithm: " << key_algorithm;
*error = KM_ERROR_INCOMPATIBLE_ALGORITHM;
return {};
}
@@ -277,12 +310,20 @@
// hardware/interfaces/keymaster/4.1/vts/functional/DeviceUniqueAttestationTest.cpp:203
// at commit 36dcf1a404a9cf07ca5a2a6ad92371507194fe1b .
if (attest_params.find(keymaster::TAG_DEVICE_UNIQUE_ATTESTATION) != -1) {
+ LOG(ERROR) << "TAG_DEVICE_UNIQUE_ATTESTATION not supported";
*error = KM_ERROR_UNIMPLEMENTED;
return {};
}
+ keymaster::AttestKeyInfo attest_key_info(attest_key, &issuer_subject, error);
+ if (*error != KM_ERROR_OK) {
+ LOG(ERROR)
+ << "Error creating attestation key info from given key and subject";
+ return {};
+ }
+
return keymaster::generate_attestation(asymmetric_key, attest_params,
- {} /* attest_key */,
+ std::move(attest_key_info),
*attestation_context_, error);
}
@@ -290,32 +331,332 @@
const keymaster::Key& key, const keymaster::AuthorizationSet& cert_params,
bool fake_signature, keymaster_error_t* error) const {
keymaster_algorithm_t key_algorithm;
- if (!key.authorizations().GetTagValue(keymaster::TAG_ALGORITHM, &key_algorithm)) {
- *error = KM_ERROR_UNKNOWN_ERROR;
- return {};
+ if (!key.authorizations().GetTagValue(keymaster::TAG_ALGORITHM,
+ &key_algorithm)) {
+ *error = KM_ERROR_UNKNOWN_ERROR;
+ return {};
}
if ((key_algorithm != KM_ALGORITHM_RSA && key_algorithm != KM_ALGORITHM_EC)) {
- *error = KM_ERROR_INCOMPATIBLE_ALGORITHM;
- return {};
+ *error = KM_ERROR_INCOMPATIBLE_ALGORITHM;
+ return {};
}
- // We have established that the given key has the correct algorithm, and because this is the
- // SoftKeymasterContext we can assume that the Key is an AsymmetricKey. So we can downcast.
+ // We have established that the given key has the correct algorithm, and
+ // because this is the SoftKeymasterContext we can assume that the Key is an
+ // AsymmetricKey. So we can downcast.
const keymaster::AsymmetricKey& asymmetric_key =
static_cast<const keymaster::AsymmetricKey&>(key);
- return generate_self_signed_cert(asymmetric_key, cert_params, fake_signature, error);
+ return generate_self_signed_cert(asymmetric_key, cert_params, fake_signature,
+ error);
}
keymaster_error_t TpmKeymasterContext::UnwrapKey(
- const KeymasterKeyBlob&,
- const KeymasterKeyBlob&,
- const AuthorizationSet&,
- const KeymasterKeyBlob&,
- AuthorizationSet*,
- keymaster_key_format_t*,
- KeymasterKeyBlob*) const {
- LOG(ERROR) << "TODO(b/155697375): Implement UnwrapKey";
- return KM_ERROR_UNIMPLEMENTED;
+ const KeymasterKeyBlob& wrapped_key_blob,
+ const KeymasterKeyBlob& wrapping_key_blob,
+ const AuthorizationSet& wrapping_key_params,
+ const KeymasterKeyBlob& masking_key, AuthorizationSet* wrapped_key_params,
+ keymaster_key_format_t* wrapped_key_format,
+ KeymasterKeyBlob* wrapped_key_material) const {
+ keymaster_error_t error = KM_ERROR_OK;
+
+ if (wrapped_key_material == nullptr) {
+ return KM_ERROR_UNEXPECTED_NULL_POINTER;
+ }
+
+ // Parse wrapping key.
+ keymaster::UniquePtr<keymaster::Key> wrapping_key;
+ error = ParseKeyBlob(wrapping_key_blob, wrapping_key_params, &wrapping_key);
+ if (error != KM_ERROR_OK) {
+ return error;
+ }
+
+ keymaster::AuthProxy wrapping_key_auths(wrapping_key->hw_enforced(),
+ wrapping_key->sw_enforced());
+
+ // Check Wrapping Key Purpose
+ if (!wrapping_key_auths.Contains(keymaster::TAG_PURPOSE, KM_PURPOSE_WRAP)) {
+ LOG(ERROR) << "Wrapping key did not have KM_PURPOSE_WRAP";
+ return KM_ERROR_INCOMPATIBLE_PURPOSE;
+ }
+
+ // Check Padding mode is RSA_OAEP and digest is SHA_2_256 (spec
+ // mandated)
+ if (!wrapping_key_auths.Contains(keymaster::TAG_DIGEST,
+ KM_DIGEST_SHA_2_256)) {
+ LOG(ERROR) << "Wrapping key lacks authorization for SHA2-256";
+ return KM_ERROR_INCOMPATIBLE_DIGEST;
+ }
+ if (!wrapping_key_auths.Contains(keymaster::TAG_PADDING, KM_PAD_RSA_OAEP)) {
+ LOG(ERROR) << "Wrapping key lacks authorization for padding OAEP";
+ return KM_ERROR_INCOMPATIBLE_PADDING_MODE;
+ }
+
+ // Check that that was also the padding mode and digest specified
+ if (!wrapping_key_params.Contains(keymaster::TAG_DIGEST,
+ KM_DIGEST_SHA_2_256)) {
+ LOG(ERROR) << "Wrapping key must use SHA2-256";
+ return KM_ERROR_INCOMPATIBLE_DIGEST;
+ }
+ if (!wrapping_key_params.Contains(keymaster::TAG_PADDING, KM_PAD_RSA_OAEP)) {
+ LOG(ERROR) << "Wrapping key must use OAEP padding";
+ return KM_ERROR_INCOMPATIBLE_PADDING_MODE;
+ }
+
+ // Parse wrapped key data.
+ KeymasterBlob iv;
+ KeymasterKeyBlob transit_key;
+ KeymasterKeyBlob secure_key;
+ KeymasterBlob tag;
+ KeymasterBlob wrapped_key_description;
+ error = parse_wrapped_key(wrapped_key_blob, &iv, &transit_key, &secure_key,
+ &tag, wrapped_key_params, wrapped_key_format,
+ &wrapped_key_description);
+ if (error != KM_ERROR_OK) {
+ return error;
+ }
+
+ // Decrypt encryptedTransportKey (transit_key) with wrapping_key
+ keymaster::OperationFactory* operation_factory =
+ wrapping_key->key_factory()->GetOperationFactory(KM_PURPOSE_DECRYPT);
+ if (operation_factory == NULL) {
+ return KM_ERROR_UNKNOWN_ERROR;
+ }
+
+ AuthorizationSet out_params;
+ keymaster::OperationPtr operation(operation_factory->CreateOperation(
+ std::move(*wrapping_key), wrapping_key_params, &error));
+ if ((operation.get() == nullptr) || (error != KM_ERROR_OK)) {
+ return error;
+ }
+
+ error = operation->Begin(wrapping_key_params, &out_params);
+ if (error != KM_ERROR_OK) {
+ return error;
+ }
+
+ keymaster::Buffer input;
+ if (!input.Reinitialize(transit_key.key_material,
+ transit_key.key_material_size)) {
+ return KM_ERROR_MEMORY_ALLOCATION_FAILED;
+ }
+
+ keymaster::Buffer output;
+ error = operation->Finish(wrapping_key_params, input,
+ keymaster::Buffer() /* signature */, &out_params,
+ &output);
+ if (error != KM_ERROR_OK) {
+ return error;
+ }
+
+ // decrypt the encrypted key material with the transit key
+ KeymasterKeyBlob transport_key = {output.peek_read(),
+ output.available_read()};
+
+ // XOR the transit key with the masking key
+ if (transport_key.key_material_size != masking_key.key_material_size) {
+ return KM_ERROR_INVALID_ARGUMENT;
+ }
+ for (size_t i = 0; i < transport_key.key_material_size; i++) {
+ transport_key.writable_data()[i] ^= masking_key.key_material[i];
+ }
+
+ auto transport_key_authorizations =
+ keymaster::AuthorizationSetBuilder()
+ .AesEncryptionKey(256)
+ .Padding(KM_PAD_NONE)
+ .Authorization(keymaster::TAG_BLOCK_MODE, KM_MODE_GCM)
+ .Authorization(keymaster::TAG_NONCE, iv)
+ .Authorization(keymaster::TAG_MIN_MAC_LENGTH, 128)
+ .build();
+ if (transport_key_authorizations.is_valid() != AuthorizationSet::Error::OK) {
+ return TranslateAuthorizationSetError(
+ transport_key_authorizations.is_valid());
+ }
+
+ auto gcm_params = keymaster::AuthorizationSetBuilder()
+ .Padding(KM_PAD_NONE)
+ .Authorization(keymaster::TAG_BLOCK_MODE, KM_MODE_GCM)
+ .Authorization(keymaster::TAG_NONCE, iv)
+ .Authorization(keymaster::TAG_MAC_LENGTH, 128)
+ .build();
+ if (gcm_params.is_valid() != AuthorizationSet::Error::OK) {
+ return TranslateAuthorizationSetError(
+ transport_key_authorizations.is_valid());
+ }
+
+ auto aes_factory = GetKeyFactory(KM_ALGORITHM_AES);
+ if (!aes_factory) {
+ return KM_ERROR_UNKNOWN_ERROR;
+ }
+
+ keymaster::UniquePtr<keymaster::Key> aes_transport_key;
+ error = aes_factory->LoadKey(std::move(transport_key), gcm_params,
+ std::move(transport_key_authorizations),
+ AuthorizationSet(), &aes_transport_key);
+ if (error != KM_ERROR_OK) {
+ return error;
+ }
+
+ keymaster::OperationFactory* aes_operation_factory =
+ GetOperationFactory(KM_ALGORITHM_AES, KM_PURPOSE_DECRYPT);
+ if (!aes_operation_factory) {
+ return KM_ERROR_UNKNOWN_ERROR;
+ }
+
+ keymaster::OperationPtr aes_operation(aes_operation_factory->CreateOperation(
+ std::move(*aes_transport_key), gcm_params, &error));
+ if (!aes_operation.get()) {
+ return error;
+ }
+
+ error = aes_operation->Begin(gcm_params, &out_params);
+ if (error != KM_ERROR_OK) {
+ return error;
+ }
+
+ size_t total_key_size = secure_key.key_material_size + tag.data_length;
+ keymaster::Buffer plaintext_key;
+ if (!plaintext_key.Reinitialize(total_key_size)) {
+ return KM_ERROR_MEMORY_ALLOCATION_FAILED;
+ }
+ keymaster::Buffer encrypted_key;
+ if (!encrypted_key.Reinitialize(total_key_size)) {
+ return KM_ERROR_MEMORY_ALLOCATION_FAILED;
+ }
+
+ // Concatenate key data and authentication tag.
+ if (!encrypted_key.write(secure_key.key_material,
+ secure_key.key_material_size)) {
+ return KM_ERROR_UNKNOWN_ERROR;
+ }
+ if (!encrypted_key.write(tag.data, tag.data_length)) {
+ return KM_ERROR_UNKNOWN_ERROR;
+ }
+
+ auto update_params = keymaster::AuthorizationSetBuilder()
+ .Authorization(keymaster::TAG_ASSOCIATED_DATA,
+ wrapped_key_description.data,
+ wrapped_key_description.data_length)
+ .build();
+ if (update_params.is_valid() != AuthorizationSet::Error::OK) {
+ return TranslateAuthorizationSetError(update_params.is_valid());
+ }
+
+ size_t update_consumed = 0;
+ AuthorizationSet update_outparams;
+ error = aes_operation->Update(update_params, encrypted_key, &update_outparams,
+ &plaintext_key, &update_consumed);
+ if (error != KM_ERROR_OK) {
+ return error;
+ }
+
+ AuthorizationSet finish_params;
+ AuthorizationSet finish_out_params;
+ keymaster::Buffer finish_input;
+ error = aes_operation->Finish(finish_params, finish_input,
+ keymaster::Buffer() /* signature */,
+ &finish_out_params, &plaintext_key);
+ if (error != KM_ERROR_OK) {
+ return error;
+ }
+
+ *wrapped_key_material = {plaintext_key.peek_read(),
+ plaintext_key.available_read()};
+ if (!wrapped_key_material->key_material && plaintext_key.peek_read()) {
+ return KM_ERROR_MEMORY_ALLOCATION_FAILED;
+ }
+
+ return error;
}
+
+keymaster::RemoteProvisioningContext*
+TpmKeymasterContext::GetRemoteProvisioningContext() const {
+ return remote_provisioning_context_.get();
+}
+
+std::string ToHexString(const std::vector<uint8_t>& binary) {
+ std::string hex;
+ hex.reserve(binary.size() * 2);
+ for (uint8_t byte : binary) {
+ char buf[8];
+ snprintf(buf, sizeof(buf), "%02x", byte);
+ hex.append(buf);
+ }
+ return hex;
+}
+
+keymaster_error_t TpmKeymasterContext::SetVerifiedBootInfo(
+ std::string_view verified_boot_state, std::string_view bootloader_state,
+ const std::vector<uint8_t>& vbmeta_digest) {
+ if (verified_boot_state_ && verified_boot_state != *verified_boot_state_) {
+ LOG(ERROR) << "Invalid set verified boot state attempt. "
+ << "Old verified boot state: \"" << *verified_boot_state_
+ << "\","
+ << "new verified boot state: \"" << verified_boot_state << "\"";
+ return KM_ERROR_INVALID_ARGUMENT;
+ }
+ if (bootloader_state_ && bootloader_state != *bootloader_state_) {
+ LOG(ERROR) << "Invalid set bootloader state attempt. "
+ << "Old bootloader state: \"" << *bootloader_state_ << "\","
+ << "new bootloader state: \"" << bootloader_state << "\"";
+ return KM_ERROR_INVALID_ARGUMENT;
+ }
+ if (vbmeta_digest_ && vbmeta_digest != *vbmeta_digest_) {
+ LOG(ERROR) << "Invalid set vbmeta digest state attempt. "
+ << "Old vbmeta digest state: \"" << ToHexString(*vbmeta_digest_)
+ << "\","
+ << "new vbmeta digest state: \"" << ToHexString(vbmeta_digest)
+ << "\"";
+ return KM_ERROR_INVALID_ARGUMENT;
+ }
+ verified_boot_state_ = verified_boot_state;
+ bootloader_state_ = bootloader_state;
+ vbmeta_digest_ = vbmeta_digest;
+ attestation_context_->SetVerifiedBootInfo(verified_boot_state,
+ bootloader_state, vbmeta_digest);
+ remote_provisioning_context_->SetVerifiedBootInfo(
+ verified_boot_state, bootloader_state, vbmeta_digest);
+ return KM_ERROR_OK;
+}
+
+keymaster_error_t TpmKeymasterContext::SetVendorPatchlevel(
+ uint32_t vendor_patchlevel) {
+ if (vendor_patchlevel_.has_value() &&
+ vendor_patchlevel != vendor_patchlevel_.value()) {
+ // Can't set patchlevel to a different value.
+ LOG(ERROR) << "Invalid set vendor patchlevel attempt. Old patchlevel: \""
+ << *vendor_patchlevel_ << "\", new patchlevel: \""
+ << vendor_patchlevel << "\"";
+ return KM_ERROR_INVALID_ARGUMENT;
+ }
+ vendor_patchlevel_ = vendor_patchlevel;
+ remote_provisioning_context_->SetVendorPatchlevel(vendor_patchlevel);
+ return key_blob_maker_->SetVendorPatchlevel(*vendor_patchlevel_);
+}
+
+keymaster_error_t TpmKeymasterContext::SetBootPatchlevel(
+ uint32_t boot_patchlevel) {
+ if (boot_patchlevel_.has_value() &&
+ boot_patchlevel != boot_patchlevel_.value()) {
+ // Can't set patchlevel to a different value.
+ LOG(ERROR) << "Invalid set boot patchlevel attempt. Old patchlevel: \""
+ << *boot_patchlevel_ << "\", new patchlevel: \""
+ << boot_patchlevel << "\"";
+ return KM_ERROR_INVALID_ARGUMENT;
+ }
+ boot_patchlevel_ = boot_patchlevel;
+ remote_provisioning_context_->SetBootPatchlevel(boot_patchlevel);
+ return key_blob_maker_->SetBootPatchlevel(*boot_patchlevel_);
+}
+
+std::optional<uint32_t> TpmKeymasterContext::GetVendorPatchlevel() const {
+ return vendor_patchlevel_;
+}
+
+std::optional<uint32_t> TpmKeymasterContext::GetBootPatchlevel() const {
+ return boot_patchlevel_;
+}
+
+} // namespace cuttlefish
diff --git a/host/commands/secure_env/tpm_keymaster_context.h b/host/commands/secure_env/tpm_keymaster_context.h
index 54ba973..dbcdcb4 100644
--- a/host/commands/secure_env/tpm_keymaster_context.h
+++ b/host/commands/secure_env/tpm_keymaster_context.h
@@ -23,10 +23,13 @@
#include "tpm_attestation_record.h"
+namespace cuttlefish {
+
class TpmAttestationRecordContext;
class TpmResourceManager;
class TpmKeyBlobMaker;
class TpmRandomSource;
+class TpmRemoteProvisioningContext;
/**
* Implementation of KeymasterContext that wraps its keys with a TPM.
@@ -35,17 +38,25 @@
* https://cs.android.com/android/platform/superproject/+/master:system/keymaster/include/keymaster/keymaster_context.h;drc=821acb74d7febb886a9b7cefee4ee3df4cc8c556
*/
class TpmKeymasterContext : public keymaster::KeymasterContext {
-private:
+ private:
TpmResourceManager& resource_manager_;
keymaster::KeymasterEnforcement& enforcement_;
std::unique_ptr<TpmKeyBlobMaker> key_blob_maker_;
std::unique_ptr<TpmRandomSource> random_source_;
std::unique_ptr<TpmAttestationRecordContext> attestation_context_;
- std::map<keymaster_algorithm_t, std::unique_ptr<keymaster::KeyFactory>> key_factories_;
+ std::unique_ptr<TpmRemoteProvisioningContext> remote_provisioning_context_;
+ std::map<keymaster_algorithm_t, std::unique_ptr<keymaster::KeyFactory>>
+ key_factories_;
std::vector<keymaster_algorithm_t> supported_algorithms_;
uint32_t os_version_;
uint32_t os_patchlevel_;
-public:
+ std::optional<uint32_t> vendor_patchlevel_;
+ std::optional<uint32_t> boot_patchlevel_;
+ std::optional<std::string> bootloader_state_;
+ std::optional<std::string> verified_boot_state_;
+ std::optional<std::vector<uint8_t>> vbmeta_digest_;
+
+ public:
TpmKeymasterContext(TpmResourceManager&, keymaster::KeymasterEnforcement&);
~TpmKeymasterContext() = default;
@@ -53,14 +64,14 @@
return attestation_context_->GetKmVersion();
}
- keymaster_error_t SetSystemVersion(
- uint32_t os_version, uint32_t os_patchlevel) override;
- void GetSystemVersion(
- uint32_t* os_version, uint32_t* os_patchlevel) const override;
+ keymaster_error_t SetSystemVersion(uint32_t os_version,
+ uint32_t os_patchlevel) override;
+ void GetSystemVersion(uint32_t* os_version,
+ uint32_t* os_patchlevel) const override;
const keymaster::KeyFactory* GetKeyFactory(
keymaster_algorithm_t algorithm) const override;
- const keymaster::OperationFactory* GetOperationFactory(
+ keymaster::OperationFactory* GetOperationFactory(
keymaster_algorithm_t algorithm,
keymaster_purpose_t purpose) const override;
const keymaster_algorithm_t* GetSupportedAlgorithms(
@@ -76,11 +87,15 @@
const keymaster::AuthorizationSet& additional_params,
keymaster::UniquePtr<keymaster::Key>* key) const override;
- keymaster_error_t AddRngEntropy(
- const uint8_t* buf, size_t length) const override;
+ keymaster_error_t AddRngEntropy(const uint8_t* buf,
+ size_t length) const override;
keymaster::KeymasterEnforcement* enforcement_policy() override;
+ keymaster::AttestationContext* attestation_context() override {
+ return attestation_context_.get();
+ }
+
keymaster::CertificateChain GenerateAttestation(
const keymaster::Key& key,
const keymaster::AuthorizationSet& attest_params,
@@ -89,10 +104,8 @@
keymaster_error_t* error) const override;
keymaster::CertificateChain GenerateSelfSignedCertificate(
- const keymaster::Key& key,
- const keymaster::AuthorizationSet& cert_params,
- bool fake_signature,
- keymaster_error_t* error) const override;
+ const keymaster::Key& key, const keymaster::AuthorizationSet& cert_params,
+ bool fake_signature, keymaster_error_t* error) const override;
keymaster_error_t UnwrapKey(
const keymaster::KeymasterKeyBlob& wrapped_key_blob,
@@ -102,4 +115,18 @@
keymaster::AuthorizationSet* wrapped_key_params,
keymaster_key_format_t* wrapped_key_format,
keymaster::KeymasterKeyBlob* wrapped_key_material) const override;
+
+ keymaster::RemoteProvisioningContext* GetRemoteProvisioningContext()
+ const override;
+
+ keymaster_error_t SetVerifiedBootInfo(
+ std::string_view verified_boot_state, std::string_view bootloader_state,
+ const std::vector<uint8_t>& vbmeta_digest) override;
+
+ keymaster_error_t SetVendorPatchlevel(uint32_t vendor_patchlevel) override;
+ keymaster_error_t SetBootPatchlevel(uint32_t boot_patchlevel) override;
+ std::optional<uint32_t> GetVendorPatchlevel() const override;
+ std::optional<uint32_t> GetBootPatchlevel() const override;
};
+
+} // namespace cuttlefish
diff --git a/host/commands/secure_env/tpm_keymaster_enforcement.cpp b/host/commands/secure_env/tpm_keymaster_enforcement.cpp
index f5b8903..a5368c5 100644
--- a/host/commands/secure_env/tpm_keymaster_enforcement.cpp
+++ b/host/commands/secure_env/tpm_keymaster_enforcement.cpp
@@ -15,21 +15,21 @@
#include "host/commands/secure_env/tpm_keymaster_enforcement.h"
+#include <android-base/endian.h>
#include <android-base/logging.h>
-#if defined(__BIONIC__)
-#include <sys/endian.h> // for be64toh
-#endif
#include "host/commands/secure_env/primary_key_builder.h"
#include "host/commands/secure_env/tpm_hmac.h"
#include "host/commands/secure_env/tpm_key_blob_maker.h"
#include "host/commands/secure_env/tpm_random_source.h"
-using keymaster::km_id_t;
+namespace cuttlefish {
+
using keymaster::HmacSharingParameters;
using keymaster::HmacSharingParametersArray;
using keymaster::KeymasterBlob;
using keymaster::KeymasterEnforcement;
+using keymaster::km_id_t;
using keymaster::VerifyAuthorizationRequest;
using keymaster::VerifyAuthorizationResponse;
namespace {
@@ -46,9 +46,9 @@
}
} // namespace
class CompareHmacSharingParams {
-public:
- bool operator()(
- const HmacSharingParameters& a, const HmacSharingParameters& b) const {
+ public:
+ bool operator()(const HmacSharingParameters& a,
+ const HmacSharingParameters& b) const {
if (a.seed.data_length != b.seed.data_length) {
return a.seed.data_length < b.seed.data_length;
}
@@ -86,11 +86,9 @@
TpmResourceManager& resource_manager, TpmGatekeeper& gatekeeper)
: KeymasterEnforcement(64, 64),
resource_manager_(resource_manager),
- gatekeeper_(gatekeeper) {
-}
+ gatekeeper_(gatekeeper) {}
-TpmKeymasterEnforcement::~TpmKeymasterEnforcement() {
-}
+TpmKeymasterEnforcement::~TpmKeymasterEnforcement() {}
bool TpmKeymasterEnforcement::activation_date_valid(
uint64_t activation_date) const {
@@ -99,13 +97,13 @@
bool TpmKeymasterEnforcement::expiration_date_passed(
uint64_t expiration_date) const {
- return expiration_date > get_wall_clock_time_ms();
+ return expiration_date < get_wall_clock_time_ms();
}
-bool TpmKeymasterEnforcement::auth_token_timed_out(
- const hw_auth_token_t& token, uint32_t timeout) const {
+bool TpmKeymasterEnforcement::auth_token_timed_out(const hw_auth_token_t& token,
+ uint32_t timeout) const {
// timeout comes in seconds, token.timestamp comes in milliseconds
- uint64_t timeout_ms = 1000 * (uint64_t) timeout;
+ uint64_t timeout_ms = 1000 * (uint64_t)timeout;
return (be64toh(token.timestamp) + timeout_ms) < get_current_time_ms();
}
@@ -132,32 +130,24 @@
* GateKeeper::MintAuthToken
*/
- const uint8_t *auth_token_key = nullptr;
+ const uint8_t* auth_token_key = nullptr;
uint32_t auth_token_key_len = 0;
if (!gatekeeper_.GetAuthTokenKey(&auth_token_key, &auth_token_key_len)) {
LOG(WARNING) << "Unable to get gatekeeper auth token";
return false;
}
+ constexpr uint32_t hashable_length =
+ sizeof(token.version) + sizeof(token.challenge) + sizeof(token.user_id) +
+ sizeof(token.authenticator_id) + sizeof(token.authenticator_type) +
+ sizeof(token.timestamp);
- constexpr uint32_t hashable_length = sizeof(token.version) +
- sizeof(token.challenge) +
- sizeof(token.user_id) +
- sizeof(token.authenticator_id) +
- sizeof(token.authenticator_type) +
- sizeof(token.timestamp);
-
- static_assert(
- offsetof(hw_auth_token_t, hmac) == hashable_length,
- "hw_auth_token_t does not appear to be packed");
-
+ static_assert(offsetof(hw_auth_token_t, hmac) == hashable_length,
+ "hw_auth_token_t does not appear to be packed");
gatekeeper_.ComputeSignature(
- comparison_token.hmac,
- sizeof(comparison_token.hmac),
- auth_token_key,
- auth_token_key_len,
- reinterpret_cast<uint8_t*>(&comparison_token),
+ comparison_token.hmac, sizeof(comparison_token.hmac), auth_token_key,
+ auth_token_key_len, reinterpret_cast<uint8_t*>(&comparison_token),
hashable_length);
static_assert(sizeof(token.hmac) == sizeof(comparison_token.hmac));
@@ -170,9 +160,8 @@
if (!have_saved_params_) {
saved_params_.seed = {};
TpmRandomSource random_source{resource_manager_.Esys()};
- auto rc =
- random_source.GenerateRandom(
- saved_params_.nonce, sizeof(saved_params_.nonce));
+ auto rc = random_source.GenerateRandom(saved_params_.nonce,
+ sizeof(saved_params_.nonce));
if (rc != KM_ERROR_OK) {
LOG(ERROR) << "Failed to generate HmacSharingParameters nonce";
return rc;
@@ -185,18 +174,15 @@
}
keymaster_error_t TpmKeymasterEnforcement::ComputeSharedHmac(
- const HmacSharingParametersArray& hmac_array,
- KeymasterBlob* sharingCheck) {
+ const HmacSharingParametersArray& hmac_array, KeymasterBlob* sharingCheck) {
std::set<HmacSharingParameters, CompareHmacSharingParams> sorted_hmac_inputs;
bool found_mine = false;
for (int i = 0; i < hmac_array.num_params; i++) {
HmacSharingParameters sharing_params;
sharing_params.seed =
keymaster::KeymasterBlob(hmac_array.params_array[i].seed);
- memcpy(
- sharing_params.nonce,
- hmac_array.params_array[i].nonce,
- sizeof(sharing_params.nonce));
+ memcpy(sharing_params.nonce, hmac_array.params_array[i].nonce,
+ sizeof(sharing_params.nonce));
found_mine = found_mine || (sharing_params == saved_params_);
sorted_hmac_inputs.emplace(std::move(sharing_params));
}
@@ -218,7 +204,6 @@
}
}
-
auto signing_key_builder = PrimaryKeyBuilder();
signing_key_builder.SigningKey();
signing_key_builder.UniqueData(std::string(unique_data, sizeof(unique_data)));
@@ -230,12 +215,9 @@
static const uint8_t signing_input[] = "Keymaster HMAC Verification";
- auto hmac = TpmHmac(
- resource_manager_,
- signing_key->get(),
- TpmAuth(ESYS_TR_PASSWORD),
- signing_input,
- sizeof(signing_input));
+ auto hmac =
+ TpmHmac(resource_manager_, signing_key->get(), TpmAuth(ESYS_TR_PASSWORD),
+ signing_input, sizeof(signing_input));
if (!hmac) {
LOG(ERROR) << "Unable to complete signing check";
@@ -260,10 +242,10 @@
response.token.timestamp = get_current_time_ms();
response.token.security_level = SecurityLevel();
- VerificationData verify_data {
- .challenge = response.token.challenge,
- .timestamp = response.token.timestamp,
- .security_level = response.token.security_level,
+ VerificationData verify_data{
+ .challenge = response.token.challenge,
+ .timestamp = response.token.timestamp,
+ .security_level = response.token.security_level,
};
auto signing_key_builder = PrimaryKeyBuilder();
@@ -274,12 +256,9 @@
LOG(ERROR) << "Could not make signing key for verifying authorization";
return response;
}
- auto hmac = TpmHmac(
- resource_manager_,
- signing_key->get(),
- TpmAuth(ESYS_TR_PASSWORD),
- reinterpret_cast<uint8_t*>(&verify_data),
- sizeof(verify_data));
+ auto hmac =
+ TpmHmac(resource_manager_, signing_key->get(), TpmAuth(ESYS_TR_PASSWORD),
+ reinterpret_cast<uint8_t*>(&verify_data), sizeof(verify_data));
if (!hmac) {
LOG(ERROR) << "Could not calculate verification hmac";
@@ -294,18 +273,56 @@
return response;
}
-bool TpmKeymasterEnforcement::CreateKeyId(
- const keymaster_key_blob_t& key_blob, km_id_t* keyid) const {
- keymaster::AuthorizationSet hw_enforced;
- keymaster::AuthorizationSet sw_enforced;
- keymaster::KeymasterKeyBlob key_material;
- auto rc =
- TpmKeyBlobMaker(resource_manager_)
- .UnwrapKeyBlob(key_blob, &hw_enforced, &sw_enforced, &key_material);
- if (rc != KM_ERROR_OK) {
- LOG(ERROR) << "Could not unwrap key: " << rc;
- return false;
+keymaster_error_t TpmKeymasterEnforcement::GenerateTimestampToken(
+ keymaster::TimestampToken* token) {
+ token->timestamp = get_current_time_ms();
+ token->security_level = SecurityLevel();
+ token->mac = KeymasterBlob();
+
+ auto signing_key_builder = PrimaryKeyBuilder();
+ signing_key_builder.SigningKey();
+ signing_key_builder.UniqueData("timestamp_token");
+ auto signing_key = signing_key_builder.CreateKey(resource_manager_);
+ if (!signing_key) {
+ LOG(ERROR) << "Could not make signing key for verifying authorization";
+ return KM_ERROR_UNKNOWN_ERROR;
}
+ std::vector<uint8_t> token_buf_to_sign(token->SerializedSize(), 0);
+ auto hmac =
+ TpmHmac(resource_manager_, signing_key->get(), TpmAuth(ESYS_TR_PASSWORD),
+ token_buf_to_sign.data(), token_buf_to_sign.size());
+ if (!hmac) {
+ LOG(ERROR) << "Could not calculate timestamp token hmac";
+ return KM_ERROR_UNKNOWN_ERROR;
+ } else if (hmac->size == 0) {
+ LOG(ERROR) << "hmac was too short";
+ return KM_ERROR_UNKNOWN_ERROR;
+ }
+ token->mac = KeymasterBlob(hmac->buffer, hmac->size);
+
+ return KM_ERROR_OK;
+}
+
+keymaster::KmErrorOr<std::array<uint8_t, 32>>
+TpmKeymasterEnforcement::ComputeHmac(
+ const std::vector<uint8_t>& data_to_mac) const {
+ std::array<uint8_t, 32> result;
+
+ const uint8_t* auth_token_key = nullptr;
+ uint32_t auth_token_key_len = 0;
+ if (!gatekeeper_.GetAuthTokenKey(&auth_token_key, &auth_token_key_len)) {
+ LOG(WARNING) << "Unable to get gatekeeper auth token";
+ return KM_ERROR_UNKNOWN_ERROR;
+ }
+
+ gatekeeper_.ComputeSignature(result.data(), result.size(), auth_token_key,
+ auth_token_key_len, data_to_mac.data(),
+ data_to_mac.size());
+ return result;
+}
+
+bool TpmKeymasterEnforcement::CreateKeyId(const keymaster_key_blob_t& key_blob,
+ km_id_t* keyid) const {
auto signing_key_builder = PrimaryKeyBuilder();
signing_key_builder.SigningKey();
signing_key_builder.UniqueData("key_id");
@@ -314,12 +331,9 @@
LOG(ERROR) << "Could not make signing key for key id";
return false;
}
- auto hmac = TpmHmac(
- resource_manager_,
- signing_key->get(),
- TpmAuth(ESYS_TR_PASSWORD),
- key_material.key_material,
- key_material.key_material_size);
+ auto hmac =
+ TpmHmac(resource_manager_, signing_key->get(), TpmAuth(ESYS_TR_PASSWORD),
+ key_blob.key_material, key_blob.key_material_size);
if (!hmac) {
LOG(ERROR) << "Failed to make a signature for a key id";
return false;
@@ -332,3 +346,5 @@
memcpy(keyid, hmac->buffer, sizeof(km_id_t));
return true;
}
+
+} // namespace cuttlefish
diff --git a/host/commands/secure_env/tpm_keymaster_enforcement.h b/host/commands/secure_env/tpm_keymaster_enforcement.h
index 1d6a1e5..1178932 100644
--- a/host/commands/secure_env/tpm_keymaster_enforcement.h
+++ b/host/commands/secure_env/tpm_keymaster_enforcement.h
@@ -20,28 +20,30 @@
#include "host/commands/secure_env/tpm_gatekeeper.h"
#include "host/commands/secure_env/tpm_resource_manager.h"
+namespace cuttlefish {
+
/**
* Implementation of keymaster::KeymasterEnforcement that depends on having a
* TPM available. See the definitions in
* system/keymaster/include/keymaster/keymaster_enforcement.h
*/
class TpmKeymasterEnforcement : public keymaster::KeymasterEnforcement {
-public:
- TpmKeymasterEnforcement(
- TpmResourceManager& resource_manager, TpmGatekeeper& gatekeeper);
+ public:
+ TpmKeymasterEnforcement(TpmResourceManager& resource_manager,
+ TpmGatekeeper& gatekeeper);
~TpmKeymasterEnforcement();
bool activation_date_valid(uint64_t activation_date) const override;
bool expiration_date_passed(uint64_t expiration_date) const override;
- bool auth_token_timed_out(
- const hw_auth_token_t& token, uint32_t timeout) const override;
+ bool auth_token_timed_out(const hw_auth_token_t& token,
+ uint32_t timeout) const override;
uint64_t get_current_time_ms() const override;
keymaster_security_level_t SecurityLevel() const override;
bool ValidateTokenSignature(const hw_auth_token_t& token) const override;
keymaster_error_t GetHmacSharingParameters(
- keymaster::HmacSharingParameters* params) override;
+ keymaster::HmacSharingParameters* params) override;
keymaster_error_t ComputeSharedHmac(
const keymaster::HmacSharingParametersArray& params_array,
keymaster::KeymasterBlob* sharingCheck) override;
@@ -49,13 +51,20 @@
keymaster::VerifyAuthorizationResponse VerifyAuthorization(
const keymaster::VerifyAuthorizationRequest& request) override;
- bool CreateKeyId(
- const keymaster_key_blob_t& key_blob,
- keymaster::km_id_t* keyid) const override;
+ keymaster_error_t GenerateTimestampToken(
+ keymaster::TimestampToken* token) override;
-private:
+ keymaster::KmErrorOr<std::array<uint8_t, 32>> ComputeHmac(
+ const std::vector<uint8_t>& data_to_mac) const override;
+
+ bool CreateKeyId(const keymaster_key_blob_t& key_blob,
+ keymaster::km_id_t* keyid) const override;
+
+ private:
TpmResourceManager& resource_manager_;
TpmGatekeeper& gatekeeper_;
bool have_saved_params_ = false;
keymaster::HmacSharingParameters saved_params_;
};
+
+} // namespace cuttlefish
diff --git a/host/commands/secure_env/tpm_random_source.cpp b/host/commands/secure_env/tpm_random_source.cpp
index 8695e4a..569edbc 100644
--- a/host/commands/secure_env/tpm_random_source.cpp
+++ b/host/commands/secure_env/tpm_random_source.cpp
@@ -19,6 +19,8 @@
#include "tss2/tss2_esys.h"
#include "tss2/tss2_rc.h"
+namespace cuttlefish {
+
TpmRandomSource::TpmRandomSource(ESYS_CONTEXT* esys) : esys_(esys) {
}
@@ -62,6 +64,11 @@
keymaster_error_t TpmRandomSource::AddRngEntropy(
const uint8_t* buffer, size_t size) const {
+ if (size > 2048) {
+ // IKeyMintDevice.aidl specifies that there's an upper limit of 2KiB.
+ return KM_ERROR_INVALID_INPUT_LENGTH;
+ }
+
TPM2B_SENSITIVE_DATA in_data;
while (size > MAX_STIR_RANDOM_BUFFER_SIZE) {
memcpy(in_data.buffer, buffer, MAX_STIR_RANDOM_BUFFER_SIZE);
@@ -97,3 +104,5 @@
}
return KM_ERROR_OK;
}
+
+} // namespace cuttlefish
diff --git a/host/commands/secure_env/tpm_random_source.h b/host/commands/secure_env/tpm_random_source.h
index 2e7d8a1..c9a91c7 100644
--- a/host/commands/secure_env/tpm_random_source.h
+++ b/host/commands/secure_env/tpm_random_source.h
@@ -19,6 +19,8 @@
struct ESYS_CONTEXT;
+namespace cuttlefish {
+
/**
* Secure random number generator, pulling data from a TPM.
*
@@ -36,3 +38,5 @@
private:
ESYS_CONTEXT* esys_;
};
+
+} // namespace cuttlefish
diff --git a/host/commands/secure_env/tpm_remote_provisioning_context.cpp b/host/commands/secure_env/tpm_remote_provisioning_context.cpp
new file mode 100644
index 0000000..87bb172
--- /dev/null
+++ b/host/commands/secure_env/tpm_remote_provisioning_context.cpp
@@ -0,0 +1,232 @@
+/*
+ * Copyright 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <algorithm>
+#include <cassert>
+#include <optional>
+
+#include <android-base/logging.h>
+#include <keymaster/cppcose/cppcose.h>
+#include <openssl/bn.h>
+#include <openssl/ec.h>
+#include <openssl/err.h>
+#include <openssl/hkdf.h>
+#include <openssl/rand.h>
+
+#include "host/commands/secure_env/primary_key_builder.h"
+#include "host/commands/secure_env/tpm_hmac.h"
+#include "tpm_remote_provisioning_context.h"
+#include "tpm_resource_manager.h"
+
+using namespace cppcose;
+
+namespace cuttlefish {
+
+TpmRemoteProvisioningContext::TpmRemoteProvisioningContext(
+ TpmResourceManager& resource_manager)
+ : resource_manager_(resource_manager) {
+ std::tie(devicePrivKey_, bcc_) = GenerateBcc(/*testMode=*/false);
+}
+
+std::vector<uint8_t> TpmRemoteProvisioningContext::DeriveBytesFromHbk(
+ const std::string& context, size_t num_bytes) const {
+ PrimaryKeyBuilder key_builder;
+ key_builder.SigningKey();
+ key_builder.UniqueData("HardwareBoundKey");
+ TpmObjectSlot key = key_builder.CreateKey(resource_manager_);
+
+ auto hbk =
+ TpmHmac(resource_manager_, key->get(), TpmAuth(ESYS_TR_PASSWORD),
+ reinterpret_cast<const uint8_t*>(context.data()), context.size());
+
+ std::vector<uint8_t> result(num_bytes);
+ if (!HKDF(result.data(), num_bytes, //
+ EVP_sha256(), //
+ hbk->buffer, hbk->size, //
+ nullptr /* salt */, 0 /* salt len */, //
+ reinterpret_cast<const uint8_t*>(context.data()), context.size())) {
+ // Should never fail. Even if it could the API has no way of reporting the
+ // error.
+ LOG(ERROR) << "Error calculating HMAC: " << ERR_peek_last_error();
+ }
+
+ return result;
+}
+
+std::unique_ptr<cppbor::Map> TpmRemoteProvisioningContext::CreateDeviceInfo()
+ const {
+ auto result = std::make_unique<cppbor::Map>();
+ result->add(cppbor::Tstr("brand"), cppbor::Tstr("Google"));
+ result->add(cppbor::Tstr("manufacturer"), cppbor::Tstr("Google"));
+ result->add(cppbor::Tstr("product"),
+ cppbor::Tstr("Cuttlefish Virtual Device"));
+ result->add(cppbor::Tstr("model"), cppbor::Tstr("Virtual Device"));
+ result->add(cppbor::Tstr("device"), cppbor::Tstr("Virtual Device"));
+ if (bootloader_state_) {
+ result->add(cppbor::Tstr("bootloader_state"),
+ cppbor::Tstr(*bootloader_state_));
+ }
+ if (verified_boot_state_) {
+ result->add(cppbor::Tstr("vb_state"), cppbor::Tstr(*verified_boot_state_));
+ }
+ if (vbmeta_digest_) {
+ result->add(cppbor::Tstr("vbmeta_digest"), cppbor::Bstr(*vbmeta_digest_));
+ }
+ if (os_version_) {
+ result->add(cppbor::Tstr("os_version"),
+ cppbor::Tstr(std::to_string(*os_version_)));
+ }
+ if (os_patchlevel_) {
+ result->add(cppbor::Tstr("system_patch_level"),
+ cppbor::Uint(*os_patchlevel_));
+ }
+ if (boot_patchlevel_) {
+ result->add(cppbor::Tstr("boot_patch_level"),
+ cppbor::Uint(*boot_patchlevel_));
+ }
+ if (vendor_patchlevel_) {
+ result->add(cppbor::Tstr("vendor_patch_level"),
+ cppbor::Uint(*vendor_patchlevel_));
+ }
+ result->add(cppbor::Tstr("version"), cppbor::Uint(2));
+ result->add(cppbor::Tstr("fused"), cppbor::Uint(0));
+ result->add(cppbor::Tstr("security_level"), cppbor::Tstr("tee"));
+ result->canonicalize();
+ return result;
+}
+
+std::pair<std::vector<uint8_t> /* privKey */, cppbor::Array /* BCC */>
+TpmRemoteProvisioningContext::GenerateBcc(bool testMode) const {
+ std::vector<uint8_t> privKey(ED25519_PRIVATE_KEY_LEN);
+ std::vector<uint8_t> pubKey(ED25519_PUBLIC_KEY_LEN);
+
+ std::vector<uint8_t> seed;
+ if (testMode) {
+ // Length is hard-coded in the BoringCrypto API without a constant
+ seed.resize(32);
+ RAND_bytes(seed.data(), seed.size());
+ } else {
+ // TODO: Switch to P256 signing keys that are TPM-bound.
+ seed = DeriveBytesFromHbk("BccKey", 32);
+ }
+ ED25519_keypair_from_seed(pubKey.data(), privKey.data(), seed.data());
+
+ auto coseKey = cppbor::Map()
+ .add(CoseKey::KEY_TYPE, OCTET_KEY_PAIR)
+ .add(CoseKey::ALGORITHM, EDDSA)
+ .add(CoseKey::CURVE, ED25519)
+ .add(CoseKey::KEY_OPS, VERIFY)
+ .add(CoseKey::PUBKEY_X, pubKey)
+ .canonicalize();
+ auto sign1Payload =
+ cppbor::Map()
+ .add(1 /* Issuer */, "Issuer")
+ .add(2 /* Subject */, "Subject")
+ .add(-4670552 /* Subject Pub Key */, coseKey.encode())
+ .add(-4670553 /* Key Usage (little-endian order) */,
+ std::vector<uint8_t>{0x20} /* keyCertSign = 1<<5 */)
+ .canonicalize()
+ .encode();
+ auto coseSign1 = constructCoseSign1(privKey, /* signing key */
+ cppbor::Map(), /* extra protected */
+ sign1Payload, {} /* AAD */);
+ assert(coseSign1);
+
+ return {privKey,
+ cppbor::Array().add(std::move(coseKey)).add(coseSign1.moveValue())};
+}
+
+void TpmRemoteProvisioningContext::SetSystemVersion(uint32_t os_version,
+ uint32_t os_patchlevel) {
+ os_version_ = os_version;
+ os_patchlevel_ = os_patchlevel;
+}
+
+void TpmRemoteProvisioningContext::SetVendorPatchlevel(
+ uint32_t vendor_patchlevel) {
+ vendor_patchlevel_ = vendor_patchlevel;
+}
+
+void TpmRemoteProvisioningContext::SetBootPatchlevel(uint32_t boot_patchlevel) {
+ boot_patchlevel_ = boot_patchlevel;
+}
+
+void TpmRemoteProvisioningContext::SetVerifiedBootInfo(
+ std::string_view boot_state, std::string_view bootloader_state,
+ const std::vector<uint8_t>& vbmeta_digest) {
+ verified_boot_state_ = boot_state;
+ bootloader_state_ = bootloader_state;
+ vbmeta_digest_ = vbmeta_digest;
+}
+
+ErrMsgOr<std::vector<uint8_t>>
+TpmRemoteProvisioningContext::BuildProtectedDataPayload(
+ bool isTestMode, //
+ const std::vector<uint8_t>& macKey, //
+ const std::vector<uint8_t>& aad) const {
+ std::vector<uint8_t> devicePrivKey;
+ cppbor::Array bcc;
+ if (isTestMode) {
+ std::tie(devicePrivKey, bcc) = GenerateBcc(/*testMode=*/true);
+ } else {
+ devicePrivKey = devicePrivKey_;
+ auto clone = bcc_.clone();
+ if (!clone->asArray()) {
+ return "The BCC is not an array";
+ }
+ bcc = std::move(*clone->asArray());
+ }
+ auto sign1 = constructCoseSign1(devicePrivKey, macKey, aad);
+ if (!sign1) {
+ return sign1.moveMessage();
+ }
+ return cppbor::Array().add(sign1.moveValue()).add(std::move(bcc)).encode();
+}
+
+std::optional<cppcose::HmacSha256>
+TpmRemoteProvisioningContext::GenerateHmacSha256(
+ const cppcose::bytevec& input) const {
+ auto signing_key_builder = PrimaryKeyBuilder();
+ signing_key_builder.SigningKey();
+ signing_key_builder.UniqueData("Public Key Authentication Key");
+ auto signing_key = signing_key_builder.CreateKey(resource_manager_);
+ if (!signing_key) {
+ LOG(ERROR) << "Could not make MAC key for authenticating the pubkey";
+ return std::nullopt;
+ }
+
+ auto tpm_digest =
+ TpmHmac(resource_manager_, signing_key->get(), TpmAuth(ESYS_TR_PASSWORD),
+ input.data(), input.size());
+
+ if (!tpm_digest) {
+ LOG(ERROR) << "Could not calculate hmac";
+ return std::nullopt;
+ }
+
+ cppcose::HmacSha256 hmac;
+ if (tpm_digest->size != hmac.size()) {
+ LOG(ERROR) << "TPM-generated digest was too short. Actual size: "
+ << tpm_digest->size << " expected " << hmac.size() << " bytes";
+ return std::nullopt;
+ }
+
+ std::copy(tpm_digest->buffer, tpm_digest->buffer + tpm_digest->size,
+ hmac.begin());
+ return hmac;
+}
+
+} // namespace cuttlefish
diff --git a/host/commands/secure_env/tpm_remote_provisioning_context.h b/host/commands/secure_env/tpm_remote_provisioning_context.h
new file mode 100644
index 0000000..9c9e359
--- /dev/null
+++ b/host/commands/secure_env/tpm_remote_provisioning_context.h
@@ -0,0 +1,66 @@
+/*
+ * Copyright 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <keymaster/remote_provisioning_context.h>
+
+#include "host/commands/secure_env/tpm_resource_manager.h"
+#include "keymaster/cppcose/cppcose.h"
+
+namespace cuttlefish {
+
+/**
+ * TPM-backed implementation of the provisioning context.
+ */
+class TpmRemoteProvisioningContext
+ : public keymaster::RemoteProvisioningContext {
+ public:
+ TpmRemoteProvisioningContext(TpmResourceManager& resource_manager);
+ ~TpmRemoteProvisioningContext() override = default;
+ std::vector<uint8_t> DeriveBytesFromHbk(const std::string& context,
+ size_t numBytes) const override;
+ std::unique_ptr<cppbor::Map> CreateDeviceInfo() const override;
+ cppcose::ErrMsgOr<std::vector<uint8_t>> BuildProtectedDataPayload(
+ bool isTestMode, //
+ const std::vector<uint8_t>& macKey, //
+ const std::vector<uint8_t>& aad) const override;
+ std::optional<cppcose::HmacSha256> GenerateHmacSha256(
+ const cppcose::bytevec& input) const override;
+ std::pair<std::vector<uint8_t>, cppbor::Array> GenerateBcc(
+ bool testMode) const;
+ void SetSystemVersion(uint32_t os_version, uint32_t os_patchlevel);
+ void SetVendorPatchlevel(uint32_t vendor_patchlevel);
+ void SetBootPatchlevel(uint32_t boot_patchlevel);
+ void SetVerifiedBootInfo(std::string_view boot_state,
+ std::string_view bootloader_state,
+ const std::vector<uint8_t>& vbmeta_digest);
+
+ private:
+ std::vector<uint8_t> devicePrivKey_;
+ cppbor::Array bcc_;
+ TpmResourceManager& resource_manager_;
+
+ std::optional<uint32_t> os_version_;
+ std::optional<uint32_t> os_patchlevel_;
+ std::optional<uint32_t> vendor_patchlevel_;
+ std::optional<uint32_t> boot_patchlevel_;
+ std::optional<std::string> verified_boot_state_;
+ std::optional<std::string> bootloader_state_;
+ std::optional<std::vector<uint8_t>> vbmeta_digest_;
+};
+
+} // namespace cuttlefish
diff --git a/host/commands/secure_env/tpm_resource_manager.cpp b/host/commands/secure_env/tpm_resource_manager.cpp
index 3651ec8..defe153 100644
--- a/host/commands/secure_env/tpm_resource_manager.cpp
+++ b/host/commands/secure_env/tpm_resource_manager.cpp
@@ -18,6 +18,8 @@
#include <android-base/logging.h>
#include <tss2/tss2_rc.h>
+namespace cuttlefish {
+
TpmResourceManager::ObjectSlot::ObjectSlot(TpmResourceManager* resource_manager)
: ObjectSlot(resource_manager, ESYS_TR_NONE) {
}
@@ -75,3 +77,5 @@
}
return TpmObjectSlot{new ObjectSlot(this)};
}
+
+} // namespace cuttlefish
diff --git a/host/commands/secure_env/tpm_resource_manager.h b/host/commands/secure_env/tpm_resource_manager.h
index e1ed83f..d4ce2f2 100644
--- a/host/commands/secure_env/tpm_resource_manager.h
+++ b/host/commands/secure_env/tpm_resource_manager.h
@@ -21,6 +21,8 @@
#include <tss2/tss2_esys.h>
+namespace cuttlefish {
+
/**
* Object slot manager for TPM memory. The TPM can only hold a fixed number of
* objects at once. Some TPM operations are defined to consume slots either
@@ -60,3 +62,5 @@
};
using TpmObjectSlot = std::shared_ptr<TpmResourceManager::ObjectSlot>;
+
+} // namespace cuttlefish
diff --git a/host/commands/secure_env/tpm_serialize.cpp b/host/commands/secure_env/tpm_serialize.cpp
index 1cf07ff..5e3fc8c 100644
--- a/host/commands/secure_env/tpm_serialize.cpp
+++ b/host/commands/secure_env/tpm_serialize.cpp
@@ -21,6 +21,8 @@
#include "tss2/tss2_mu.h"
#include "tss2/tss2_rc.h"
+namespace cuttlefish {
+
template<typename T>
int MarshalFn = 0; // Break code without an explicit specialization.
@@ -82,3 +84,5 @@
template class TpmSerializable<TPM2B_PRIVATE>;
template class TpmSerializable<TPM2B_PUBLIC>;
+
+} // namespace cuttlefish
diff --git a/host/commands/secure_env/tpm_serialize.h b/host/commands/secure_env/tpm_serialize.h
index 17884a5..4043ea3 100644
--- a/host/commands/secure_env/tpm_serialize.h
+++ b/host/commands/secure_env/tpm_serialize.h
@@ -24,6 +24,8 @@
#include <android-base/logging.h>
+namespace cuttlefish {
+
/**
* An implementation of a keymaster::Serializable type that refers to a TPM type
* by an unmanaged pointer. When the TpmSerializable serializes or deserializes
@@ -50,3 +52,5 @@
using SerializeTpmKeyPrivate = TpmSerializable<TPM2B_PRIVATE>;
using SerializeTpmKeyPublic = TpmSerializable<TPM2B_PUBLIC>;
+
+} // namespace cuttlefish
diff --git a/host/commands/launch/Android.bp b/host/commands/start/Android.bp
similarity index 87%
rename from host/commands/launch/Android.bp
rename to host/commands/start/Android.bp
index 6adf7a2..289edc3 100644
--- a/host/commands/launch/Android.bp
+++ b/host/commands/start/Android.bp
@@ -18,17 +18,20 @@
}
cc_binary {
- name: "launch_cvd",
+ name: "cvd_internal_start",
+ symlinks: ["launch_cvd"],
srcs: [
"filesystem_explorer.cc",
"flag_forwarder.cc",
- "launch_cvd.cc",
+ "main.cc",
],
shared_libs: [
+ "libext2_blkid",
"libcuttlefish_fs",
"libcuttlefish_utils",
"libbase",
"libjsoncpp",
+ "libfruit",
"libnl",
"libxml2",
"libz",
@@ -39,7 +42,8 @@
"libgflags",
],
required: [
- "mkenvimage",
+ "assemble_cvd",
+ "run_cvd",
],
defaults: ["cuttlefish_host", "cuttlefish_libicuuc"],
}
diff --git a/host/commands/launch/filesystem_explorer.cc b/host/commands/start/filesystem_explorer.cc
similarity index 100%
rename from host/commands/launch/filesystem_explorer.cc
rename to host/commands/start/filesystem_explorer.cc
diff --git a/host/commands/launch/filesystem_explorer.h b/host/commands/start/filesystem_explorer.h
similarity index 100%
rename from host/commands/launch/filesystem_explorer.h
rename to host/commands/start/filesystem_explorer.h
diff --git a/host/commands/launch/flag_forwarder.cc b/host/commands/start/flag_forwarder.cc
similarity index 99%
rename from host/commands/launch/flag_forwarder.cc
rename to host/commands/start/flag_forwarder.cc
index 692e453..aa492c5 100644
--- a/host/commands/launch/flag_forwarder.cc
+++ b/host/commands/start/flag_forwarder.cc
@@ -290,8 +290,7 @@
// Ensure this is set on by putting it at the end.
cmd.AddParameter("--helpxml");
std::string helpxml_input, helpxml_output, helpxml_error;
- cuttlefish::SubprocessOptions options;
- options.Verbose(false);
+ auto options = cuttlefish::SubprocessOptions().Verbose(false);
int helpxml_ret = cuttlefish::RunWithManagedStdio(std::move(cmd), &helpxml_input,
&helpxml_output, &helpxml_error,
options);
diff --git a/host/commands/launch/flag_forwarder.h b/host/commands/start/flag_forwarder.h
similarity index 100%
rename from host/commands/launch/flag_forwarder.h
rename to host/commands/start/flag_forwarder.h
diff --git a/host/commands/launch/launch_cvd.cc b/host/commands/start/main.cc
similarity index 98%
rename from host/commands/launch/launch_cvd.cc
rename to host/commands/start/main.cc
index f67ce48..c11ac67 100644
--- a/host/commands/launch/launch_cvd.cc
+++ b/host/commands/start/main.cc
@@ -23,13 +23,12 @@
#include "common/libs/fs/shared_buf.h"
#include "common/libs/fs/shared_fd.h"
#include "common/libs/utils/subprocess.h"
-#include "host/commands/launch/filesystem_explorer.h"
+#include "host/commands/start/filesystem_explorer.h"
+#include "host/commands/start/flag_forwarder.h"
#include "host/libs/config/cuttlefish_config.h"
#include "host/libs/config/host_tools_version.h"
#include "host/libs/config/fetcher_config.h"
-#include "flag_forwarder.h"
-
/**
* If stdin is a tty, that means a user is invoking launch_cvd on the command
* line and wants automatic file detection for assemble_cvd.
diff --git a/host/commands/stop_cvd/Android.bp b/host/commands/status/Android.bp
similarity index 90%
copy from host/commands/stop_cvd/Android.bp
copy to host/commands/status/Android.bp
index a670a25..4ce0e8c 100644
--- a/host/commands/stop_cvd/Android.bp
+++ b/host/commands/status/Android.bp
@@ -18,15 +18,17 @@
}
cc_binary {
- name: "stop_cvd",
+ name: "cvd_internal_status",
+ symlinks: ["cvd_status"],
srcs: [
"main.cc",
],
shared_libs: [
+ "libext2_blkid",
"libbase",
"libcuttlefish_fs",
"libcuttlefish_utils",
- "libcuttlefish_allocd_utils",
+ "libfruit",
"libjsoncpp",
],
static_libs: [
diff --git a/host/commands/status/main.cc b/host/commands/status/main.cc
new file mode 100644
index 0000000..a479081
--- /dev/null
+++ b/host/commands/status/main.cc
@@ -0,0 +1,174 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <fcntl.h>
+#include <inttypes.h>
+#include <limits.h>
+#include <signal.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <sys/wait.h>
+#include <unistd.h>
+
+#include <algorithm>
+#include <cstdlib>
+#include <fstream>
+#include <iomanip>
+#include <iostream>
+#include <memory>
+#include <sstream>
+#include <string>
+#include <vector>
+
+#include <android-base/logging.h>
+
+#include "common/libs/fs/shared_fd.h"
+#include "common/libs/fs/shared_select.h"
+#include "common/libs/utils/environment.h"
+#include "common/libs/utils/flag_parser.h"
+#include "common/libs/utils/tee_logging.h"
+#include "host/commands/run_cvd/runner_defs.h"
+#include "host/libs/config/cuttlefish_config.h"
+#include "host/libs/vm_manager/vm_manager.h"
+
+#define CHECK_PRINT(print, condition, message) \
+ if (print) { \
+ if (!(condition)) { \
+ std::cout << " Status: Stopped (" << message << ")" << std::endl; \
+ exit(0); \
+ } \
+ } else \
+ CHECK(condition) << message
+
+namespace cuttlefish {
+
+int CvdStatusMain(int argc, char** argv) {
+ ::android::base::InitLogging(argv, android::base::StderrLogger);
+ ::android::base::SetLogger(LogToStderrAndFiles({}));
+
+ std::vector<Flag> flags;
+
+ std::int32_t wait_for_launcher;
+ Json::Value device_info;
+ flags.emplace_back(
+ GflagsCompatFlag("wait_for_launcher", wait_for_launcher)
+ .Help("How many seconds to wait for the launcher to respond to the "
+ "status command. A value of zero means wait indefinitely"));
+ std::string instance_name;
+ flags.emplace_back(GflagsCompatFlag("instance_name", instance_name)
+ .Help("Name of the instance to check. If not "
+ "provided, DefaultInstance is used."));
+ bool print;
+ flags.emplace_back(GflagsCompatFlag("print", print)
+ .Help("If provided, prints status and instance config "
+ "information to stdout instead of CHECK"));
+ bool all_instances;
+ flags.emplace_back(
+ GflagsCompatFlag("all_instances", all_instances)
+ .Help("List all instances status and instance config information."));
+
+ flags.emplace_back(HelpFlag(flags));
+ flags.emplace_back(UnexpectedArgumentGuard());
+
+ std::vector<std::string> args =
+ ArgsToVec(argc - 1, argv + 1); // Skip argv[0]
+ CHECK(ParseFlags(flags, args)) << "Could not process command line flags.";
+
+ auto config = CuttlefishConfig::Get();
+ CHECK(config) << "Failed to obtain config object";
+
+ auto instance_names = all_instances ? config->instance_names()
+ : std::vector<std::string>{instance_name};
+
+ Json::Value devices_info;
+ for (int index = 0; index < instance_names.size(); index++) {
+ std::string instance_name = instance_names[index];
+ auto instance = instance_name.empty()
+ ? config->ForDefaultInstance()
+ : config->ForInstanceName(instance_name);
+ auto monitor_path = instance.launcher_monitor_socket_path();
+ CHECK_PRINT(print, !monitor_path.empty(),
+ "No path to launcher monitor found");
+
+ auto monitor_socket = SharedFD::SocketLocalClient(
+ monitor_path.c_str(), false, SOCK_STREAM, wait_for_launcher);
+ CHECK_PRINT(print, monitor_socket->IsOpen(),
+ "Unable to connect to launcher monitor at " + monitor_path +
+ ": " + monitor_socket->StrError());
+
+ auto request = LauncherAction::kStatus;
+ auto bytes_sent = monitor_socket->Send(&request, sizeof(request), 0);
+ CHECK_PRINT(print, bytes_sent > 0,
+ "Error sending launcher monitor the status command: " +
+ monitor_socket->StrError());
+
+ // Perform a select with a timeout to guard against launcher hanging
+ SharedFDSet read_set;
+ read_set.Set(monitor_socket);
+ struct timeval timeout = {wait_for_launcher, 0};
+ int selected = Select(&read_set, nullptr, nullptr,
+ wait_for_launcher <= 0 ? nullptr : &timeout);
+ CHECK_PRINT(
+ print, selected >= 0,
+ std::string("Failed communication with the launcher monitor: ") +
+ strerror(errno));
+ CHECK_PRINT(print, selected > 0,
+ "Timeout expired waiting for launcher monitor to respond");
+
+ LauncherResponse response;
+ auto bytes_recv = monitor_socket->Recv(&response, sizeof(response), 0);
+ CHECK_PRINT(
+ print, bytes_recv > 0,
+ std::string("Error receiving response from launcher monitor: ") +
+ monitor_socket->StrError());
+ CHECK_PRINT(print, response == LauncherResponse::kSuccess,
+ std::string("Received '") + static_cast<char>(response) +
+ "' response from launcher monitor");
+ if (print) {
+ devices_info[index]["assembly_dir"] = config->assembly_dir();
+ devices_info[index]["instance_name"] = instance.instance_name();
+ devices_info[index]["instance_dir"] = instance.instance_dir();
+ devices_info[index]["web_access"] =
+ "https://" + config->sig_server_address() + ":" +
+ std::to_string(config->sig_server_port()) +
+ "/client.html?deviceId=" + instance.instance_name();
+ devices_info[index]["adb_serial"] = instance.adb_ip_and_port();
+ devices_info[index]["webrtc_port"] =
+ std::to_string(config->sig_server_port());
+ for (int i = 0; i < config->display_configs().size(); i++) {
+ devices_info[index]["displays"][i] =
+ std::to_string(config->display_configs()[i].width) + " x " +
+ std::to_string(config->display_configs()[i].height) + " ( " +
+ std::to_string(config->display_configs()[i].dpi) + " )";
+ }
+ devices_info[index]["status"] = "Running";
+ if (index == (instance_names.size() - 1)) {
+ std::cout << devices_info.toStyledString() << std::endl;
+ }
+ } else {
+ LOG(INFO) << "run_cvd is active.";
+ }
+ }
+ return 0;
+}
+} // namespace cuttlefish
+
+int main(int argc, char** argv) {
+ return cuttlefish::CvdStatusMain(argc, argv);
+}
diff --git a/host/commands/stop_cvd/Android.bp b/host/commands/stop/Android.bp
similarity index 91%
rename from host/commands/stop_cvd/Android.bp
rename to host/commands/stop/Android.bp
index a670a25..7d705fd 100644
--- a/host/commands/stop_cvd/Android.bp
+++ b/host/commands/stop/Android.bp
@@ -18,15 +18,18 @@
}
cc_binary {
- name: "stop_cvd",
+ name: "cvd_internal_stop",
+ symlinks: ["stop_cvd"],
srcs: [
"main.cc",
],
shared_libs: [
+ "libext2_blkid",
"libbase",
"libcuttlefish_fs",
"libcuttlefish_utils",
"libcuttlefish_allocd_utils",
+ "libfruit",
"libjsoncpp",
],
static_libs: [
diff --git a/host/commands/stop_cvd/main.cc b/host/commands/stop/main.cc
similarity index 61%
rename from host/commands/stop_cvd/main.cc
rename to host/commands/stop/main.cc
index 9e4fdd4..44ed5ec 100644
--- a/host/commands/stop_cvd/main.cc
+++ b/host/commands/stop/main.cc
@@ -37,30 +37,27 @@
#include <vector>
#include <android-base/strings.h>
-#include <gflags/gflags.h>
#include <android-base/logging.h>
#include "common/libs/fs/shared_fd.h"
#include "common/libs/fs/shared_select.h"
#include "common/libs/utils/environment.h"
+#include "common/libs/utils/files.h"
+#include "common/libs/utils/flag_parser.h"
+#include "common/libs/utils/result.h"
#include "host/commands/run_cvd/runner_defs.h"
#include "host/libs/allocd/request.h"
#include "host/libs/allocd/utils.h"
#include "host/libs/config/cuttlefish_config.h"
#include "host/libs/vm_manager/vm_manager.h"
-DEFINE_int32(wait_for_launcher, 5,
- "How many seconds to wait for the launcher to respond to the stop "
- "command. A value of zero means wait indefinetly");
-
namespace cuttlefish {
namespace {
-std::set<std::string> FallbackPaths() {
+std::set<std::string> FallbackDirs() {
std::set<std::string> paths;
std::string parent_path = StringFromEnv("HOME", ".");
paths.insert(parent_path + "/cuttlefish_assembly");
- paths.insert(parent_path + "/cuttlefish_assembly/*");
std::unique_ptr<DIR, int(*)(DIR*)> dir(opendir(parent_path.c_str()), closedir);
for (auto entity = readdir(dir.get()); entity != nullptr; entity = readdir(dir.get())) {
@@ -68,43 +65,26 @@
if (!android::base::StartsWith(subdir, "cuttlefish_runtime.")) {
continue;
}
- auto instance_dir = parent_path + "/" + subdir;
- // Add the instance directory
- paths.insert(instance_dir);
- // Add files in instance dir
- paths.insert(instance_dir + "/*");
- // Add files in the tombstone directory
- paths.insert(instance_dir + "/tombstones/*");
- // Add files in the internal directory
- paths.insert(instance_dir + "/" + std::string(kInternalDirName) + "/*");
- // Add files in the shared directory
- paths.insert(instance_dir + "/" + std::string(kSharedDirName) + "/*");
+ paths.insert(parent_path + "/" + subdir);
}
return paths;
}
-std::set<std::string> PathsForInstance(const CuttlefishConfig& config,
- const CuttlefishConfig::InstanceSpecific instance) {
+std::set<std::string> DirsForInstance(
+ const CuttlefishConfig& config,
+ const CuttlefishConfig::InstanceSpecific instance) {
return {
- config.assembly_dir(),
- config.assembly_dir() + "/*",
- instance.instance_dir(),
- instance.PerInstancePath("*"),
- instance.PerInstancePath("tombstones"),
- instance.PerInstancePath("tombstones/*"),
- instance.instance_internal_dir(),
- instance.PerInstanceInternalPath("*"),
- instance.PerInstancePath(kSharedDirName),
- instance.PerInstancePath(kSharedDirName) + "/*",
+ config.assembly_dir(),
+ instance.instance_dir(),
};
}
// Gets a set of the possible process groups of a previous launch
-std::set<pid_t> GetCandidateProcessGroups(const std::set<std::string>& paths) {
+std::set<pid_t> GetCandidateProcessGroups(const std::set<std::string>& dirs) {
std::stringstream cmd;
cmd << "lsof -t 2>/dev/null";
- for (const auto& path : paths) {
- cmd << " " << path;
+ for (const auto& dir : dirs) {
+ cmd << " +D " << dir;
}
std::string cmd_str = cmd.str();
std::shared_ptr<FILE> cmd_out(popen(cmd_str.c_str(), "r"), pclose);
@@ -128,10 +108,10 @@
return ret;
}
-int FallBackStop(const std::set<std::string>& paths) {
+int FallBackStop(const std::set<std::string>& dirs) {
auto exit_code = 1; // Having to fallback is an error
- auto process_groups = GetCandidateProcessGroups(paths);
+ auto process_groups = GetCandidateProcessGroups(dirs);
for (auto pgid: process_groups) {
LOG(INFO) << "Sending SIGKILL to process group " << pgid;
auto retval = killpg(pgid, SIGKILL);
@@ -145,62 +125,52 @@
return exit_code;
}
-bool CleanStopInstance(const CuttlefishConfig::InstanceSpecific& instance) {
+Result<void> CleanStopInstance(
+ const CuttlefishConfig::InstanceSpecific& instance,
+ std::int32_t wait_for_launcher) {
auto monitor_path = instance.launcher_monitor_socket_path();
- if (monitor_path.empty()) {
- LOG(ERROR) << "No path to launcher monitor found";
- return false;
- }
+ CF_EXPECT(!monitor_path.empty(), "No path to launcher monitor found");
+
auto monitor_socket = SharedFD::SocketLocalClient(
- monitor_path.c_str(), false, SOCK_STREAM, FLAGS_wait_for_launcher);
- if (!monitor_socket->IsOpen()) {
- LOG(ERROR) << "Unable to connect to launcher monitor at " << monitor_path
- << ": " << monitor_socket->StrError();
- return false;
- }
+ monitor_path.c_str(), false, SOCK_STREAM, wait_for_launcher);
+ CF_EXPECT(monitor_socket->IsOpen(),
+ "Unable to connect to launcher monitor at "
+ << monitor_path << ": " << monitor_socket->StrError());
+
auto request = LauncherAction::kStop;
auto bytes_sent = monitor_socket->Send(&request, sizeof(request), 0);
- if (bytes_sent < 0) {
- LOG(ERROR) << "Error sending launcher monitor the stop command: "
- << monitor_socket->StrError();
- return false;
- }
+ CF_EXPECT(bytes_sent >= 0, "Error sending launcher monitor the stop command: "
+ << monitor_socket->StrError());
+
// Perform a select with a timeout to guard against launcher hanging
SharedFDSet read_set;
read_set.Set(monitor_socket);
- struct timeval timeout = {FLAGS_wait_for_launcher, 0};
+ struct timeval timeout = {wait_for_launcher, 0};
int selected = Select(&read_set, nullptr, nullptr,
- FLAGS_wait_for_launcher <= 0 ? nullptr : &timeout);
- if (selected < 0){
- LOG(ERROR) << "Failed communication with the launcher monitor: "
- << strerror(errno);
- return false;
- }
- if (selected == 0) {
- LOG(ERROR) << "Timeout expired waiting for launcher monitor to respond";
- return false;
- }
+ wait_for_launcher <= 0 ? nullptr : &timeout);
+ CF_EXPECT(selected >= 0, "Failed communication with the launcher monitor: "
+ << strerror(errno));
+ CF_EXPECT(selected > 0, "Timeout expired waiting for launcher to respond");
+
LauncherResponse response;
auto bytes_recv = monitor_socket->Recv(&response, sizeof(response), 0);
- if (bytes_recv < 0) {
- LOG(ERROR) << "Error receiving response from launcher monitor: "
- << monitor_socket->StrError();
- return false;
- }
- if (response != LauncherResponse::kSuccess) {
- LOG(ERROR) << "Received '" << static_cast<char>(response)
- << "' response from launcher monitor";
- return false;
- }
- LOG(INFO) << "Successfully stopped device " << instance.adb_ip_and_port();
- return true;
+ CF_EXPECT(bytes_recv >= 0, "Error receiving response from launcher monitor: "
+ << monitor_socket->StrError());
+ CF_EXPECT(response == LauncherResponse::kSuccess,
+ "Received '" << static_cast<char>(response)
+ << "' response from launcher monitor");
+ LOG(INFO) << "Successfully stopped device " << instance.instance_name()
+ << ": " << instance.adb_ip_and_port();
+ return {};
}
int StopInstance(const CuttlefishConfig& config,
- const CuttlefishConfig::InstanceSpecific& instance) {
- bool res = CleanStopInstance(instance);
- if (!res) {
- return FallBackStop(PathsForInstance(config, instance));
+ const CuttlefishConfig::InstanceSpecific& instance,
+ std::int32_t wait_for_launcher) {
+ auto res = CleanStopInstance(instance, wait_for_launcher);
+ if (!res.ok()) {
+ LOG(ERROR) << "Clean stop failed: " << res.error();
+ return FallBackStop(DirsForInstance(config, instance));
}
return 0;
}
@@ -230,18 +200,35 @@
int StopCvdMain(int argc, char** argv) {
::android::base::InitLogging(argv, android::base::StderrLogger);
- google::ParseCommandLineFlags(&argc, &argv, true);
+
+ std::vector<Flag> flags;
+
+ std::int32_t wait_for_launcher = 5;
+ flags.emplace_back(
+ GflagsCompatFlag("wait_for_launcher", wait_for_launcher)
+ .Help("How many seconds to wait for the launcher to respond to the "
+ "status command. A value of zero means wait indefinitely"));
+ bool clear_instance_dirs;
+ flags.emplace_back(
+ GflagsCompatFlag("clear_instance_dirs", clear_instance_dirs)
+ .Help("If provided, deletes the instance dir after attempting to "
+ "stop each instance."));
+ flags.emplace_back(HelpFlag(flags));
+ flags.emplace_back(UnexpectedArgumentGuard());
+ std::vector<std::string> args =
+ ArgsToVec(argc - 1, argv + 1); // Skip argv[0]
+ CHECK(ParseFlags(flags, args)) << "Could not process command line flags.";
auto config = CuttlefishConfig::Get();
if (!config) {
LOG(ERROR) << "Failed to obtain config object";
- return FallBackStop(FallbackPaths());
+ return FallBackStop(FallbackDirs());
}
int ret = 0;
for (const auto& instance : config->Instances()) {
auto session_id = instance.session_id();
- int exit_status = StopInstance(*config, instance);
+ int exit_status = StopInstance(*config, instance, wait_for_launcher);
if (exit_status == 0 && instance.use_allocd()) {
// only release session resources if the instance was stopped
SharedFD allocd_sock =
@@ -254,6 +241,14 @@
ReleaseAllocdResources(allocd_sock, session_id);
}
+ if (clear_instance_dirs) {
+ if (DirectoryExists(instance.instance_dir())) {
+ LOG(INFO) << "Deleting instance dir " << instance.instance_dir();
+ if (!RecursivelyRemoveDirectory(instance.instance_dir())) {
+ LOG(ERROR) << "Unable to rmdir " << instance.instance_dir();
+ }
+ }
+ }
ret |= exit_status;
}
diff --git a/host/commands/tapsetiff/tapsetiff.py b/host/commands/tapsetiff/tapsetiff.py
deleted file mode 100755
index 8a0999e..0000000
--- a/host/commands/tapsetiff/tapsetiff.py
+++ /dev/null
@@ -1,30 +0,0 @@
-#!/usr/bin/python3
-
-# Copyright (C) 2020 The Android Open Source Project
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http:#www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-import fcntl
-import struct
-import sys
-
-TUNSETIFF = 0x400454ca
-IFF_TAP = 0x0002
-IFF_NO_PI = 0x1000
-IFF_VNET_HDR = 0x4000
-
-tun_fd = int(sys.argv[1])
-tap_name = sys.argv[2]
-
-ifr = struct.pack('16sH', tap_name.encode('utf-8'), IFF_TAP | IFF_NO_PI | IFF_VNET_HDR)
-fcntl.ioctl(tun_fd, TUNSETIFF, ifr)
diff --git a/host/commands/test_gce_driver/Android.bp b/host/commands/test_gce_driver/Android.bp
new file mode 100644
index 0000000..c59f582
--- /dev/null
+++ b/host/commands/test_gce_driver/Android.bp
@@ -0,0 +1,87 @@
+//
+// Copyright (C) 2021 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package {
+ default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+cc_binary {
+ name: "cvd_test_gce_driver",
+ srcs: [
+ "cvd_test_gce_driver.cpp",
+ "gce_api.cpp",
+ "key_pair.cpp",
+ "scoped_instance.cpp",
+ ],
+ static_libs: [
+ "libcuttlefish_web",
+ "libcuttlefish_host_config",
+ "libcuttlefish_test_gce_proto_cpp",
+ "libgflags",
+ "libext2_blkid",
+ ],
+ target: {
+ host: {
+ stl: "libc++_static",
+ static_libs: [
+ "libbase",
+ "libcuttlefish_fs",
+ "libcuttlefish_utils",
+ "libcurl",
+ "libcrypto",
+ "libext2_uuid",
+ "liblog",
+ "libssl",
+ "libz",
+ "libjsoncpp",
+ "libprotobuf-cpp-full",
+ ],
+ },
+ android: {
+ shared_libs: [
+ "libbase",
+ "libcuttlefish_fs",
+ "libcuttlefish_utils",
+ "libcurl",
+ "libcrypto",
+ "libext2_uuid",
+ "liblog",
+ "libssl",
+ "libz",
+ "libjsoncpp",
+ "libprotobuf-cpp-full",
+ ],
+ },
+ },
+ defaults: ["cuttlefish_host"],
+}
+
+cc_library_static {
+ name: "libcuttlefish_test_gce_proto_cpp",
+ proto: {
+ export_proto_headers: true,
+ type: "full",
+ },
+ srcs: ["test_gce_driver.proto"],
+ defaults: ["cuttlefish_host"],
+}
+
+java_library_host {
+ name: "libcuttlefish_test_gce_proto_java",
+ proto: {
+ type: "full",
+ },
+ srcs: ["test_gce_driver.proto"],
+}
diff --git a/host/commands/test_gce_driver/cvd_test_gce_driver.cpp b/host/commands/test_gce_driver/cvd_test_gce_driver.cpp
new file mode 100644
index 0000000..d037478
--- /dev/null
+++ b/host/commands/test_gce_driver/cvd_test_gce_driver.cpp
@@ -0,0 +1,410 @@
+//
+// Copyright (C) 2021 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include <curl/curl.h>
+#include <sys/stat.h>
+#include <unistd.h>
+
+#include <fstream>
+#include <functional>
+#include <iomanip>
+#include <iostream>
+#include <iterator>
+#include <optional>
+#include <string>
+#include <unordered_map>
+
+#include <android-base/logging.h>
+#include <android-base/result.h>
+#include <android-base/strings.h>
+#include <google/protobuf/io/zero_copy_stream_impl.h>
+#include <google/protobuf/text_format.h>
+#include <google/protobuf/util/delimited_message_util.h>
+
+#include "common/libs/fs/shared_buf.h"
+#include "common/libs/fs/shared_fd.h"
+#include "common/libs/fs/shared_select.h"
+#include "common/libs/utils/archive.h"
+#include "common/libs/utils/files.h"
+#include "common/libs/utils/flag_parser.h"
+#include "common/libs/utils/subprocess.h"
+#include "host/commands/test_gce_driver/gce_api.h"
+#include "host/commands/test_gce_driver/key_pair.h"
+#include "host/commands/test_gce_driver/scoped_instance.h"
+#include "host/libs/web/build_api.h"
+#include "host/libs/web/credential_source.h"
+#include "host/libs/web/curl_wrapper.h"
+#include "host/libs/web/install_zip.h"
+
+#include "test_gce_driver.pb.h"
+
+using android::base::Error;
+using android::base::Result;
+
+using google::protobuf::util::ParseDelimitedFromZeroCopyStream;
+using google::protobuf::util::SerializeDelimitedToFileDescriptor;
+
+namespace cuttlefish {
+namespace {
+
+Result<Json::Value> ReadJsonFromFile(const std::string& path) {
+ Json::CharReaderBuilder builder;
+ std::ifstream ifs(path);
+ Json::Value content;
+ std::string errorMessage;
+ CF_EXPECT(Json::parseFromStream(builder, ifs, &content, &errorMessage),
+ "Could not read config file \"" << path << "\": " << errorMessage);
+ return content;
+}
+
+class ReadEvalPrintLoop {
+ public:
+ ReadEvalPrintLoop(GceApi& gce, BuildApi& build, int in_fd, int out_fd,
+ bool internal_addresses)
+ : gce_(gce),
+ build_(build),
+ in_(in_fd),
+ out_(out_fd),
+ internal_addresses_(internal_addresses) {}
+
+ Result<void> Process() {
+ while (true) {
+ test_gce_driver::TestMessage msg;
+ bool clean_eof;
+ LOG(DEBUG) << "Waiting for message";
+ bool parsed = ParseDelimitedFromZeroCopyStream(&msg, &in_, &clean_eof);
+ LOG(DEBUG) << "Received message";
+ if (clean_eof) {
+ return {};
+ } else if (!parsed) {
+ return Error() << "Failed to parse input message";
+ }
+ Result<void> handler_result;
+ switch (msg.contents_case()) {
+ case test_gce_driver::TestMessage::ContentsCase::kExit: {
+ test_gce_driver::TestMessage stream_end_msg;
+ stream_end_msg.mutable_exit(); // Set this in the oneof
+ if (!SerializeDelimitedToFileDescriptor(stream_end_msg, out_)) {
+ return Error() << "Failure while writing stream end message";
+ }
+ return {};
+ }
+ case test_gce_driver::TestMessage::ContentsCase::kStreamEnd:
+ continue;
+ case test_gce_driver::TestMessage::ContentsCase::kCreateInstance:
+ handler_result = NewInstance(msg.create_instance());
+ break;
+ case test_gce_driver::TestMessage::ContentsCase::kSshCommand:
+ handler_result = SshCommand(msg.ssh_command());
+ break;
+ case test_gce_driver::TestMessage::ContentsCase::kUploadBuildArtifact:
+ handler_result = UploadBuildArtifact(msg.upload_build_artifact());
+ break;
+ case test_gce_driver::TestMessage::ContentsCase::kUploadFile:
+ handler_result = UploadFile(msg.upload_file());
+ break;
+ default: {
+ std::string msg_txt;
+ if (google::protobuf::TextFormat::PrintToString(msg, &msg_txt)) {
+ handler_result = Error()
+ << "Unexpected message: \"" << msg_txt << "\"";
+ } else {
+ handler_result = Error() << "Unexpected message: (unprintable)";
+ }
+ }
+ }
+ if (!handler_result.ok()) {
+ test_gce_driver::TestMessage error_msg;
+ error_msg.mutable_error()->set_text(handler_result.error().message());
+ CF_EXPECT(SerializeDelimitedToFileDescriptor(error_msg, out_),
+ "Failure while writing error message: (\n"
+ << handler_result.error() << "\n)");
+ }
+ test_gce_driver::TestMessage stream_end_msg;
+ stream_end_msg.mutable_stream_end(); // Set this in the oneof
+ CF_EXPECT(SerializeDelimitedToFileDescriptor(stream_end_msg, out_));
+ }
+ return {};
+ }
+
+ private:
+ Result<void> NewInstance(const test_gce_driver::CreateInstance& request) {
+ CF_EXPECT(request.id().name() != "", "Instance name must be specified");
+ CF_EXPECT(request.id().zone() != "", "Instance zone must be specified");
+ auto instance = CF_EXPECT(ScopedGceInstance::CreateDefault(
+ gce_, request.id().zone(), request.id().name(), internal_addresses_));
+ instances_.emplace(request.id().name(), std::move(instance));
+ return {};
+ }
+ Result<void> SshCommand(const test_gce_driver::SshCommand& request) {
+ auto instance = instances_.find(request.instance().name());
+ CF_EXPECT(instance != instances_.end(),
+ "Instance \"" << request.instance().name() << "\" not found");
+ auto ssh = CF_EXPECT(instance->second->Ssh());
+ for (auto argument : request.arguments()) {
+ ssh.RemoteParameter(argument);
+ }
+
+ std::optional<Subprocess> ssh_proc;
+ SharedFD stdout_read;
+ SharedFD stderr_read;
+ { // Things created here need to be closed early
+ auto cmd = ssh.Build();
+
+ SharedFD stdout_write;
+ CF_EXPECT(SharedFD::Pipe(&stdout_read, &stdout_write));
+ cmd.RedirectStdIO(Subprocess::StdIOChannel::kStdOut, stdout_write);
+
+ SharedFD stderr_write;
+ CF_EXPECT(SharedFD::Pipe(&stderr_read, &stderr_write));
+ cmd.RedirectStdIO(Subprocess::StdIOChannel::kStdErr, stderr_write);
+
+ ssh_proc = cmd.Start();
+ }
+
+ while (stdout_read->IsOpen() || stderr_read->IsOpen()) {
+ SharedFDSet read_set;
+ if (stdout_read->IsOpen()) {
+ read_set.Set(stdout_read);
+ }
+ if (stderr_read->IsOpen()) {
+ read_set.Set(stderr_read);
+ }
+ Select(&read_set, nullptr, nullptr, nullptr);
+ if (read_set.IsSet(stdout_read)) {
+ char buffer[1 << 14];
+ auto read = stdout_read->Read(buffer, sizeof(buffer));
+ CF_EXPECT(read >= 0,
+ "Failure in reading ssh stdout: " << stdout_read->StrError());
+ if (read == 0) { // EOF
+ stdout_read = SharedFD();
+ } else {
+ test_gce_driver::TestMessage stdout_chunk;
+ stdout_chunk.mutable_data()->set_type(
+ test_gce_driver::DataType::DATA_TYPE_STDOUT);
+ stdout_chunk.mutable_data()->set_contents(buffer, read);
+ CF_EXPECT(SerializeDelimitedToFileDescriptor(stdout_chunk, out_));
+ }
+ }
+ if (read_set.IsSet(stderr_read)) {
+ char buffer[1 << 14];
+ auto read = stderr_read->Read(buffer, sizeof(buffer));
+ CF_EXPECT(read >= 0,
+ "Failure in reading ssh stderr: " << stdout_read->StrError());
+ if (read == 0) { // EOF
+ stderr_read = SharedFD();
+ } else {
+ test_gce_driver::TestMessage stderr_chunk;
+ stderr_chunk.mutable_data()->set_type(
+ test_gce_driver::DataType::DATA_TYPE_STDERR);
+ stderr_chunk.mutable_data()->set_contents(buffer, read);
+ CF_EXPECT(SerializeDelimitedToFileDescriptor(stderr_chunk, out_));
+ }
+ }
+ }
+
+ auto ret = ssh_proc->Wait();
+ test_gce_driver::TestMessage retcode_chunk;
+ retcode_chunk.mutable_data()->set_type(
+ test_gce_driver::DataType::DATA_TYPE_RETURN_CODE);
+ retcode_chunk.mutable_data()->set_contents(std::to_string(ret));
+ CF_EXPECT(SerializeDelimitedToFileDescriptor(retcode_chunk, out_));
+ return {};
+ }
+ Result<void> UploadBuildArtifact(
+ const test_gce_driver::UploadBuildArtifact& request) {
+ auto instance = instances_.find(request.instance().name());
+ CF_EXPECT(instance != instances_.end(),
+ "Instance \"" << request.instance().name() << "\" not found");
+
+ struct {
+ ScopedGceInstance* instance;
+ SharedFD ssh_in;
+ std::optional<Subprocess> ssh_proc;
+ Result<void> result;
+ } callback_state;
+
+ callback_state.instance = instance->second.get();
+
+ auto callback = [&request, &callback_state](char* data, size_t size) {
+ if (data == nullptr) {
+ auto ssh = callback_state.instance->Ssh();
+ if (!ssh.ok()) {
+ callback_state.result = CF_ERR("ssh command failed\n" << ssh.error());
+ return false;
+ }
+
+ SharedFD ssh_stdin_out;
+ if (!SharedFD::Pipe(&ssh_stdin_out, &callback_state.ssh_in)) {
+ callback_state.result = CF_ERRNO("pipe failed");
+ return false;
+ }
+
+ ssh->RemoteParameter("cat >" + request.remote_path());
+ auto command = ssh->Build();
+ command.RedirectStdIO(Subprocess::StdIOChannel::kStdIn, ssh_stdin_out);
+ callback_state.ssh_proc = command.Start();
+ } else if (WriteAll(callback_state.ssh_in, data, size) != size) {
+ callback_state.ssh_proc->Stop();
+ callback_state.result = CF_ERR("Failed to write contents\n"
+ << callback_state.ssh_in->StrError());
+ return false;
+ }
+ return true;
+ };
+
+ DeviceBuild build(request.build().id(), request.build().target());
+ CF_EXPECT(
+ build_.ArtifactToCallback(build, request.artifact_name(), callback),
+ "Failed to send file: (\n"
+ << (callback_state.result.ok()
+ ? "Unknown failure"
+ : callback_state.result.error().message() + "\n)"));
+
+ callback_state.ssh_in->Close();
+
+ if (callback_state.ssh_proc) {
+ auto ssh_ret = callback_state.ssh_proc->Wait();
+ CF_EXPECT(ssh_ret == 0, "SSH command failed with code: " << ssh_ret);
+ }
+
+ return {};
+ }
+ Result<void> UploadFile(const test_gce_driver::UploadFile& request) {
+ auto instance = instances_.find(request.instance().name());
+ CF_EXPECT(instance != instances_.end(),
+ "Instance \"" << request.instance().name() << "\" not found");
+
+ auto ssh = CF_EXPECT(instance->second->Ssh());
+
+ ssh.RemoteParameter("cat >" + request.remote_path());
+
+ auto command = ssh.Build();
+
+ SharedFD ssh_stdin_out, ssh_stdin_in;
+ CF_EXPECT(SharedFD::Pipe(&ssh_stdin_out, &ssh_stdin_in), strerror(errno));
+ command.RedirectStdIO(Subprocess::StdIOChannel::kStdIn, ssh_stdin_out);
+
+ auto ssh_proc = command.Start();
+ ssh_stdin_out->Close();
+
+ {
+ Command unused = std::move(command); // force deletion
+ }
+
+ while (true) {
+ LOG(INFO) << "upload data loop";
+ test_gce_driver::TestMessage data_msg;
+ bool clean_eof;
+ LOG(DEBUG) << "Waiting for message";
+ bool parsed =
+ ParseDelimitedFromZeroCopyStream(&data_msg, &in_, &clean_eof);
+ if (clean_eof) {
+ ssh_proc.Stop();
+ return CF_ERR("Received EOF");
+ } else if (!parsed) {
+ ssh_proc.Stop();
+ return CF_ERR("Failed to parse message");
+ } else if (data_msg.contents_case() ==
+ test_gce_driver::TestMessage::ContentsCase::kStreamEnd) {
+ break;
+ } else if (data_msg.contents_case() !=
+ test_gce_driver::TestMessage::ContentsCase::kData) {
+ ssh_proc.Stop();
+ return CF_ERR(
+ "Received wrong type of message: " << data_msg.contents_case());
+ } else if (data_msg.data().type() !=
+ test_gce_driver::DataType::DATA_TYPE_FILE_CONTENTS) {
+ ssh_proc.Stop();
+ return CF_ERR(
+ "Received unexpected data type: " << data_msg.data().type());
+ }
+ LOG(INFO) << "going to write message of size "
+ << data_msg.data().contents().size();
+ if (WriteAll(ssh_stdin_in, data_msg.data().contents()) !=
+ data_msg.data().contents().size()) {
+ ssh_proc.Stop();
+ return CF_ERR("Failed to write contents: " << ssh_stdin_in->StrError());
+ }
+ LOG(INFO) << "successfully wrote message?";
+ }
+
+ ssh_stdin_in->Close();
+
+ auto ssh_ret = ssh_proc.Wait();
+ CF_EXPECT(ssh_ret == 0, "SSH command failed with code: " << ssh_ret);
+
+ return {};
+ }
+
+ GceApi& gce_;
+ BuildApi& build_;
+ google::protobuf::io::FileInputStream in_;
+ int out_;
+ bool internal_addresses_;
+
+ std::unordered_map<std::string, std::unique_ptr<ScopedGceInstance>>
+ instances_;
+};
+
+} // namespace
+
+Result<void> TestGceDriverMain(int argc, char** argv) {
+ std::vector<Flag> flags;
+ std::string service_account_json_private_key_path = "";
+ flags.emplace_back(GflagsCompatFlag("service-account-json-private-key-path",
+ service_account_json_private_key_path));
+ std::string cloud_project = "";
+ flags.emplace_back(GflagsCompatFlag("cloud-project", cloud_project));
+ bool internal_addresses = false;
+ flags.emplace_back(
+ GflagsCompatFlag("internal-addresses", internal_addresses));
+
+ std::vector<std::string> args =
+ ArgsToVec(argc - 1, argv + 1); // Skip argv[0]
+ CF_EXPECT(ParseFlags(flags, args), "Could not process command line flags.");
+
+ auto service_json =
+ CF_EXPECT(ReadJsonFromFile(service_account_json_private_key_path));
+
+ static constexpr char COMPUTE_SCOPE[] =
+ "https://www.googleapis.com/auth/compute";
+ auto curl = CurlWrapper::Create();
+ auto gce_creds = CF_EXPECT(ServiceAccountOauthCredentialSource::FromJson(
+ *curl, service_json, COMPUTE_SCOPE));
+
+ GceApi gce(*curl, gce_creds, cloud_project);
+
+ static constexpr char BUILD_SCOPE[] =
+ "https://www.googleapis.com/auth/androidbuild.internal";
+ auto build_creds = CF_EXPECT(ServiceAccountOauthCredentialSource::FromJson(
+ *curl, service_json, BUILD_SCOPE));
+
+ BuildApi build(*curl, &build_creds);
+
+ ReadEvalPrintLoop executor(gce, build, STDIN_FILENO, STDOUT_FILENO,
+ internal_addresses);
+ LOG(INFO) << "Starting processing";
+ CF_EXPECT(executor.Process());
+
+ return {};
+}
+
+} // namespace cuttlefish
+
+int main(int argc, char** argv) {
+ auto res = cuttlefish::TestGceDriverMain(argc, argv);
+ CHECK(res.ok()) << "cvd_test_gce_driver failed: " << res.error();
+}
diff --git a/host/commands/test_gce_driver/gce_api.cpp b/host/commands/test_gce_driver/gce_api.cpp
new file mode 100644
index 0000000..4922c59
--- /dev/null
+++ b/host/commands/test_gce_driver/gce_api.cpp
@@ -0,0 +1,536 @@
+//
+// Copyright (C) 2022 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "host/commands/test_gce_driver/gce_api.h"
+
+#include <uuid.h>
+
+#include <sstream>
+#include <string>
+
+#include <android-base/logging.h>
+#include <android-base/strings.h>
+
+#include "host/libs/web/credential_source.h"
+#include "host/libs/web/curl_wrapper.h"
+
+using android::base::Error;
+using android::base::Result;
+
+namespace cuttlefish {
+
+std::optional<std::string> OptStringMember(const Json::Value& jn,
+ const std::string& name) {
+ if (!jn.isMember(name) || jn[name].type() != Json::ValueType::stringValue) {
+ return {};
+ }
+ return jn[name].asString();
+}
+
+const Json::Value* OptObjMember(const Json::Value& jn,
+ const std::string& name) {
+ if (!jn.isMember(name) || jn[name].type() != Json::ValueType::objectValue) {
+ return nullptr;
+ }
+ return &(jn[name]);
+}
+
+const Json::Value* OptArrayMember(const Json::Value& jn,
+ const std::string& name) {
+ if (!jn.isMember(name) || jn[name].type() != Json::ValueType::arrayValue) {
+ return nullptr;
+ }
+ return &(jn[name]);
+}
+
+Json::Value& EnsureObjMember(Json::Value& jn, const std::string& name) {
+ if (!jn.isMember(name) || jn[name].type() != Json::ValueType::objectValue) {
+ jn[name] = Json::Value(Json::ValueType::objectValue);
+ }
+ return jn[name];
+}
+
+Json::Value& EnsureArrayMember(Json::Value& jn, const std::string& name) {
+ if (!jn.isMember(name) || jn[name].type() != Json::ValueType::arrayValue) {
+ jn[name] = Json::Value(Json::ValueType::arrayValue);
+ }
+ return jn[name];
+}
+
+GceInstanceDisk::GceInstanceDisk(const Json::Value& json) : data_(json){};
+
+GceInstanceDisk GceInstanceDisk::EphemeralBootDisk() {
+ Json::Value initial_json(Json::ValueType::objectValue);
+ initial_json["type"] = "PERSISTENT";
+ initial_json["boot"] = true;
+ initial_json["mode"] = "READ_WRITE";
+ initial_json["autoDelete"] = true;
+ return GceInstanceDisk(initial_json);
+}
+
+constexpr char kGceDiskInitParams[] = "initializeParams";
+constexpr char kGceDiskName[] = "diskName";
+std::optional<std::string> GceInstanceDisk::Name() const {
+ const auto& init_params = OptObjMember(data_, kGceDiskInitParams);
+ if (!init_params) {
+ return {};
+ }
+ return OptStringMember(*init_params, kGceDiskName);
+}
+GceInstanceDisk& GceInstanceDisk::Name(const std::string& source) & {
+ EnsureObjMember(data_, kGceDiskInitParams)[kGceDiskName] = source;
+ return *this;
+}
+GceInstanceDisk GceInstanceDisk::Name(const std::string& source) && {
+ EnsureObjMember(data_, kGceDiskInitParams)[kGceDiskName] = source;
+ return *this;
+}
+
+constexpr char kGceDiskSourceImage[] = "sourceImage";
+std::optional<std::string> GceInstanceDisk::SourceImage() const {
+ const auto& init_params = OptObjMember(data_, kGceDiskInitParams);
+ if (!init_params) {
+ return {};
+ }
+ return OptStringMember(*init_params, kGceDiskSourceImage);
+}
+GceInstanceDisk& GceInstanceDisk::SourceImage(const std::string& source) & {
+ EnsureObjMember(data_, kGceDiskInitParams)[kGceDiskSourceImage] = source;
+ return *this;
+}
+GceInstanceDisk GceInstanceDisk::SourceImage(const std::string& source) && {
+ EnsureObjMember(data_, kGceDiskInitParams)[kGceDiskSourceImage] = source;
+ return *this;
+}
+
+constexpr char kGceDiskSizeGb[] = "diskSizeGb";
+GceInstanceDisk& GceInstanceDisk::SizeGb(uint64_t size) & {
+ EnsureObjMember(data_, kGceDiskInitParams)[kGceDiskSizeGb] = size;
+ return *this;
+}
+GceInstanceDisk GceInstanceDisk::SizeGb(uint64_t size) && {
+ EnsureObjMember(data_, kGceDiskInitParams)[kGceDiskSizeGb] = size;
+ return *this;
+}
+
+const Json::Value& GceInstanceDisk::AsJson() const { return data_; }
+
+GceNetworkInterface::GceNetworkInterface(const Json::Value& data)
+ : data_(data) {}
+
+constexpr char kGceNetworkAccessConfigs[] = "accessConfigs";
+GceNetworkInterface GceNetworkInterface::Default() {
+ Json::Value json{Json::ValueType::objectValue};
+ json["network"] = "global/networks/default";
+ Json::Value accessConfig{Json::ValueType::objectValue};
+ accessConfig["type"] = "ONE_TO_ONE_NAT";
+ accessConfig["name"] = "External NAT";
+ EnsureArrayMember(json, kGceNetworkAccessConfigs).append(accessConfig);
+ return GceNetworkInterface(json);
+}
+
+constexpr char kGceNetworkExternalIp[] = "natIP";
+std::optional<std::string> GceNetworkInterface::ExternalIp() const {
+ auto accessConfigs = OptArrayMember(data_, kGceNetworkAccessConfigs);
+ if (!accessConfigs || accessConfigs->size() < 1) {
+ return {};
+ }
+ if ((*accessConfigs)[0].type() != Json::ValueType::objectValue) {
+ return {};
+ }
+ return OptStringMember((*accessConfigs)[0], kGceNetworkExternalIp);
+}
+
+constexpr char kGceNetworkInternalIp[] = "networkIP";
+std::optional<std::string> GceNetworkInterface::InternalIp() const {
+ return OptStringMember(data_, kGceNetworkInternalIp);
+}
+
+const Json::Value& GceNetworkInterface::AsJson() const { return data_; }
+
+GceInstanceInfo::GceInstanceInfo(const Json::Value& json) : data_(json) {}
+
+constexpr char kGceZone[] = "zone";
+std::optional<std::string> GceInstanceInfo::Zone() const {
+ return OptStringMember(data_, kGceZone);
+}
+GceInstanceInfo& GceInstanceInfo::Zone(const std::string& zone) & {
+ data_[kGceZone] = zone;
+ return *this;
+}
+GceInstanceInfo GceInstanceInfo::Zone(const std::string& zone) && {
+ data_[kGceZone] = zone;
+ return *this;
+}
+
+constexpr char kGceName[] = "name";
+std::optional<std::string> GceInstanceInfo::Name() const {
+ return OptStringMember(data_, kGceName);
+}
+GceInstanceInfo& GceInstanceInfo::Name(const std::string& name) & {
+ data_[kGceName] = name;
+ return *this;
+}
+GceInstanceInfo GceInstanceInfo::Name(const std::string& name) && {
+ data_[kGceName] = name;
+ return *this;
+}
+
+constexpr char kGceMachineType[] = "machineType";
+std::optional<std::string> GceInstanceInfo::MachineType() const {
+ return OptStringMember(data_, kGceMachineType);
+}
+GceInstanceInfo& GceInstanceInfo::MachineType(const std::string& type) & {
+ data_[kGceMachineType] = type;
+ return *this;
+}
+GceInstanceInfo GceInstanceInfo::MachineType(const std::string& type) && {
+ data_[kGceMachineType] = type;
+ return *this;
+}
+
+constexpr char kGceDisks[] = "disks";
+GceInstanceInfo& GceInstanceInfo::AddDisk(const GceInstanceDisk& disk) & {
+ EnsureArrayMember(data_, kGceDisks).append(disk.AsJson());
+ return *this;
+}
+GceInstanceInfo GceInstanceInfo::AddDisk(const GceInstanceDisk& disk) && {
+ EnsureArrayMember(data_, kGceDisks).append(disk.AsJson());
+ return *this;
+}
+
+constexpr char kGceNetworkInterfaces[] = "networkInterfaces";
+GceInstanceInfo& GceInstanceInfo::AddNetworkInterface(
+ const GceNetworkInterface& net) & {
+ EnsureArrayMember(data_, kGceNetworkInterfaces).append(net.AsJson());
+ return *this;
+}
+GceInstanceInfo GceInstanceInfo::AddNetworkInterface(
+ const GceNetworkInterface& net) && {
+ EnsureArrayMember(data_, kGceNetworkInterfaces).append(net.AsJson());
+ return *this;
+}
+std::vector<GceNetworkInterface> GceInstanceInfo::NetworkInterfaces() const {
+ auto jsonNetworkInterfaces = OptArrayMember(data_, kGceNetworkInterfaces);
+ if (!jsonNetworkInterfaces) {
+ return {};
+ }
+ std::vector<GceNetworkInterface> interfaces;
+ for (const Json::Value& jsonNetworkInterface : *jsonNetworkInterfaces) {
+ interfaces.push_back(GceNetworkInterface(jsonNetworkInterface));
+ }
+ return interfaces;
+}
+
+constexpr char kGceMetadata[] = "metadata";
+constexpr char kGceMetadataItems[] = "items";
+constexpr char kGceMetadataKey[] = "key";
+constexpr char kGceMetadataValue[] = "value";
+GceInstanceInfo& GceInstanceInfo::AddMetadata(const std::string& key,
+ const std::string& value) & {
+ Json::Value item{Json::ValueType::objectValue};
+ item[kGceMetadataKey] = key;
+ item[kGceMetadataValue] = value;
+ auto& metadata = EnsureObjMember(data_, kGceMetadata);
+ EnsureArrayMember(metadata, kGceMetadataItems).append(item);
+ return *this;
+}
+GceInstanceInfo GceInstanceInfo::AddMetadata(const std::string& key,
+ const std::string& value) && {
+ Json::Value item{Json::ValueType::objectValue};
+ item[kGceMetadataKey] = key;
+ item[kGceMetadataValue] = value;
+ auto& metadata = EnsureObjMember(data_, kGceMetadata);
+ EnsureArrayMember(metadata, kGceMetadataItems).append(item);
+ return *this;
+}
+
+constexpr char kGceServiceAccounts[] = "serviceAccounts";
+constexpr char kGceScopes[] = "scopes";
+GceInstanceInfo& GceInstanceInfo::AddScope(const std::string& scope) & {
+ auto& serviceAccounts = EnsureArrayMember(data_, kGceServiceAccounts);
+ if (serviceAccounts.size() == 0) {
+ serviceAccounts.append(Json::Value(Json::ValueType::objectValue));
+ }
+ serviceAccounts[0]["email"] = "default";
+ auto& scopes = EnsureArrayMember(serviceAccounts[0], kGceScopes);
+ scopes.append(scope);
+ return *this;
+}
+GceInstanceInfo GceInstanceInfo::AddScope(const std::string& scope) && {
+ auto& serviceAccounts = EnsureArrayMember(data_, kGceServiceAccounts);
+ if (serviceAccounts.size() == 0) {
+ serviceAccounts.append(Json::Value(Json::ValueType::objectValue));
+ }
+ serviceAccounts[0]["email"] = "default";
+ auto& scopes = EnsureArrayMember(serviceAccounts[0], kGceScopes);
+ scopes.append(scope);
+ return *this;
+}
+
+const Json::Value& GceInstanceInfo::AsJson() const { return data_; }
+
+GceApi::GceApi(CurlWrapper& curl, CredentialSource& credentials,
+ const std::string& project)
+ : curl_(curl), credentials_(credentials), project_(project) {}
+
+std::vector<std::string> GceApi::Headers() {
+ return {
+ "Authorization:Bearer " + credentials_.Credential(),
+ "Content-Type: application/json",
+ };
+}
+
+class GceApi::Operation::Impl {
+ public:
+ Impl(GceApi& gce_api, std::function<Result<Json::Value>()> initial_request)
+ : gce_api_(gce_api), initial_request_(std::move(initial_request)) {
+ operation_future_ = std::async([this]() { return Run(); });
+ }
+
+ Result<bool> Run() {
+ auto initial_response = initial_request_();
+ if (!initial_response.ok()) {
+ return Error() << "Initial request failed: " << initial_response.error();
+ }
+
+ auto url = OptStringMember(*initial_response, "selfLink");
+ if (!url) {
+ return Error() << "Operation " << *initial_response
+ << " was missing `selfLink` field.";
+ }
+ url = *url + "/wait";
+ running_ = true;
+
+ while (running_) {
+ auto response =
+ gce_api_.curl_.PostToJson(*url, std::string{""}, gce_api_.Headers());
+ const auto& json = response.data;
+ Json::Value errors;
+ if (auto j_error = OptObjMember(json, "error"); j_error) {
+ if (auto j_errors = OptArrayMember(*j_error, "errors"); j_errors) {
+ errors = j_errors->size() > 0 ? *j_errors : Json::Value();
+ }
+ }
+ Json::Value warnings;
+ if (auto j_warnings = OptArrayMember(json, "warnings"); j_warnings) {
+ warnings = j_warnings->size() > 0 ? *j_warnings : Json::Value();
+ }
+ LOG(DEBUG) << "Requested operation status at \"" << *url
+ << "\", received " << json;
+ if (!response.HttpSuccess() || errors != Json::Value()) {
+ return Error() << "Error accessing \"" << *url
+ << "\". Errors: " << errors
+ << ", Warnings: " << warnings;
+ }
+ if (!json.isMember("status") ||
+ json["status"].type() != Json::ValueType::stringValue) {
+ return Error() << json << " \"status\" field invalid";
+ }
+ if (json["status"] == "DONE") {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ private:
+ GceApi& gce_api_;
+ std::function<Result<Json::Value>()> initial_request_;
+ bool running_;
+ std::future<Result<bool>> operation_future_;
+ friend class GceApi::Operation;
+};
+
+GceApi::Operation::Operation(std::unique_ptr<GceApi::Operation::Impl> impl)
+ : impl_(std::move(impl)) {}
+
+GceApi::Operation::~Operation() = default;
+
+void GceApi::Operation::StopWaiting() { impl_->running_ = false; }
+
+std::future<Result<bool>>& GceApi::Operation::Future() {
+ return impl_->operation_future_;
+}
+
+static std::string RandomUuid() {
+ uuid_t uuid;
+ uuid_generate_random(uuid);
+ std::string uuid_str = "xxxxxxxx-xxxx-Mxxx-Nxxx-xxxxxxxxxxxx";
+ uuid_unparse(uuid, uuid_str.data());
+ return uuid_str;
+}
+
+// GCE gives back full URLs for zones, but it only wants the last part in
+// requests
+static std::string SanitizeZone(const std::string& zone) {
+ auto last_slash = zone.rfind("/");
+ if (last_slash == std::string::npos) {
+ return zone;
+ }
+ return zone.substr(last_slash + 1);
+}
+
+std::future<Result<GceInstanceInfo>> GceApi::Get(
+ const GceInstanceInfo& instance) {
+ auto name = instance.Name();
+ if (!name) {
+ auto task = [json = instance.AsJson()]() -> Result<GceInstanceInfo> {
+ return Error() << "Missing a name for \"" << json << "\"";
+ };
+ return std::async(std::launch::deferred, task);
+ }
+ auto zone = instance.Zone();
+ if (!zone) {
+ auto task = [json = instance.AsJson()]() -> Result<GceInstanceInfo> {
+ return Error() << "Missing a zone for \"" << json << "\"";
+ };
+ return std::async(std::launch::deferred, task);
+ }
+ return Get(*zone, *name);
+}
+
+std::future<Result<GceInstanceInfo>> GceApi::Get(const std::string& zone,
+ const std::string& name) {
+ std::stringstream url;
+ url << "https://compute.googleapis.com/compute/v1";
+ url << "/projects/" << curl_.UrlEscape(project_);
+ url << "/zones/" << curl_.UrlEscape(SanitizeZone(zone));
+ url << "/instances/" << curl_.UrlEscape(name);
+ auto task = [this, url = url.str()]() -> Result<GceInstanceInfo> {
+ auto response = curl_.DownloadToJson(url, Headers());
+ if (!response.HttpSuccess()) {
+ return Error() << "Failed to get instance info, received "
+ << response.data << " with code " << response.http_code;
+ }
+ return GceInstanceInfo(response.data);
+ };
+ return std::async(task);
+}
+
+GceApi::Operation GceApi::Insert(const Json::Value& request) {
+ if (!request.isMember("zone") ||
+ request["zone"].type() != Json::ValueType::stringValue) {
+ auto task = [request]() -> Result<Json::Value> {
+ return Error() << "Missing a zone for \"" << request << "\"";
+ };
+ return Operation(
+ std::unique_ptr<Operation::Impl>(new Operation::Impl(*this, task)));
+ }
+ auto zone = request["zone"].asString();
+ Json::Value requestNoZone = request;
+ requestNoZone.removeMember("zone");
+ std::stringstream url;
+ url << "https://compute.googleapis.com/compute/v1";
+ url << "/projects/" << curl_.UrlEscape(project_);
+ url << "/zones/" << curl_.UrlEscape(SanitizeZone(zone));
+ url << "/instances";
+ url << "?requestId=" << RandomUuid(); // Avoid duplication on request retry
+ auto task = [this, requestNoZone, url = url.str()]() -> Result<Json::Value> {
+ auto response = curl_.PostToJson(url, requestNoZone, Headers());
+ if (!response.HttpSuccess()) {
+ return Error() << "Failed to create instance: " << response.data
+ << ". Sent request " << requestNoZone;
+ }
+ return response.data;
+ };
+ return Operation(
+ std::unique_ptr<Operation::Impl>(new Operation::Impl(*this, task)));
+}
+
+GceApi::Operation GceApi::Insert(const GceInstanceInfo& request) {
+ return Insert(request.AsJson());
+}
+
+GceApi::Operation GceApi::Reset(const std::string& zone,
+ const std::string& name) {
+ std::stringstream url;
+ url << "https://compute.googleapis.com/compute/v1";
+ url << "/projects/" << curl_.UrlEscape(project_);
+ url << "/zones/" << curl_.UrlEscape(SanitizeZone(zone));
+ url << "/instances/" << curl_.UrlEscape(name);
+ url << "/reset";
+ url << "?requestId=" << RandomUuid(); // Avoid duplication on request retry
+ auto task = [this, url = url.str()]() -> Result<Json::Value> {
+ auto response = curl_.PostToJson(url, Json::Value(), Headers());
+ if (!response.HttpSuccess()) {
+ return Error() << "Failed to create instance: " << response.data;
+ }
+ return response.data;
+ };
+ return Operation(
+ std::unique_ptr<Operation::Impl>(new Operation::Impl(*this, task)));
+}
+
+GceApi::Operation GceApi::Reset(const GceInstanceInfo& instance) {
+ auto name = instance.Name();
+ if (!name) {
+ auto task = [json = instance.AsJson()]() -> Result<Json::Value> {
+ return Error() << "Missing a name for \"" << json << "\"";
+ };
+ return Operation(
+ std::unique_ptr<Operation::Impl>(new Operation::Impl(*this, task)));
+ }
+ auto zone = instance.Zone();
+ if (!zone) {
+ auto task = [json = instance.AsJson()]() -> Result<Json::Value> {
+ return Error() << "Missing a zone for \"" << json << "\"";
+ };
+ return Operation(
+ std::unique_ptr<Operation::Impl>(new Operation::Impl(*this, task)));
+ }
+ return Reset(*zone, *name);
+}
+
+GceApi::Operation GceApi::Delete(const std::string& zone,
+ const std::string& name) {
+ std::stringstream url;
+ url << "https://compute.googleapis.com/compute/v1";
+ url << "/projects/" << curl_.UrlEscape(project_);
+ url << "/zones/" << curl_.UrlEscape(SanitizeZone(zone));
+ url << "/instances/" << curl_.UrlEscape(name);
+ url << "?requestId=" << RandomUuid(); // Avoid duplication on request retry
+ auto task = [this, url = url.str()]() -> Result<Json::Value> {
+ auto response = curl_.DeleteToJson(url, Headers());
+ if (!response.HttpSuccess()) {
+ return Error() << "Failed to delete instance: " << response.data;
+ }
+ return response.data;
+ };
+ return Operation(
+ std::unique_ptr<Operation::Impl>(new Operation::Impl(*this, task)));
+}
+
+GceApi::Operation GceApi::Delete(const GceInstanceInfo& instance) {
+ auto name = instance.Name();
+ if (!name) {
+ auto task = [json = instance.AsJson()]() -> Result<Json::Value> {
+ return Error() << "Missing a name for \"" << json << "\"";
+ };
+ return Operation(
+ std::unique_ptr<Operation::Impl>(new Operation::Impl(*this, task)));
+ }
+ auto zone = instance.Zone();
+ if (!zone) {
+ auto task = [json = instance.AsJson()]() -> Result<Json::Value> {
+ return Error() << "Missing a zone for \"" << json << "\"";
+ };
+ return Operation(
+ std::unique_ptr<Operation::Impl>(new Operation::Impl(*this, task)));
+ }
+ return Delete(*zone, *name);
+}
+
+} // namespace cuttlefish
diff --git a/host/commands/test_gce_driver/gce_api.h b/host/commands/test_gce_driver/gce_api.h
new file mode 100644
index 0000000..e4d605f
--- /dev/null
+++ b/host/commands/test_gce_driver/gce_api.h
@@ -0,0 +1,147 @@
+//
+// Copyright (C) 2022 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#pragma once
+
+#include <future>
+#include <optional>
+#include <string>
+
+#include <android-base/result.h>
+#include <json/json.h>
+
+#include "host/libs/web/credential_source.h"
+#include "host/libs/web/curl_wrapper.h"
+
+namespace cuttlefish {
+
+class GceInstanceDisk {
+ public:
+ GceInstanceDisk() = default;
+ explicit GceInstanceDisk(const Json::Value&);
+
+ static GceInstanceDisk EphemeralBootDisk();
+
+ std::optional<std::string> Name() const;
+ GceInstanceDisk& Name(const std::string&) &;
+ GceInstanceDisk Name(const std::string&) &&;
+
+ std::optional<std::string> SourceImage() const;
+ GceInstanceDisk& SourceImage(const std::string&) &;
+ GceInstanceDisk SourceImage(const std::string&) &&;
+
+ GceInstanceDisk& SizeGb(uint64_t gb) &;
+ GceInstanceDisk SizeGb(uint64_t gb) &&;
+
+ const Json::Value& AsJson() const;
+
+ private:
+ Json::Value data_;
+};
+
+class GceNetworkInterface {
+ public:
+ GceNetworkInterface() = default;
+ explicit GceNetworkInterface(const Json::Value&);
+
+ static GceNetworkInterface Default();
+
+ std::optional<std::string> ExternalIp() const;
+ std::optional<std::string> InternalIp() const;
+
+ const Json::Value& AsJson() const;
+
+ private:
+ Json::Value data_;
+};
+
+class GceInstanceInfo {
+ public:
+ GceInstanceInfo() = default;
+ explicit GceInstanceInfo(const Json::Value&);
+
+ std::optional<std::string> Zone() const;
+ GceInstanceInfo& Zone(const std::string&) &;
+ GceInstanceInfo Zone(const std::string&) &&;
+
+ std::optional<std::string> Name() const;
+ GceInstanceInfo& Name(const std::string&) &;
+ GceInstanceInfo Name(const std::string&) &&;
+
+ std::optional<std::string> MachineType() const;
+ GceInstanceInfo& MachineType(const std::string&) &;
+ GceInstanceInfo MachineType(const std::string&) &&;
+
+ GceInstanceInfo& AddDisk(const GceInstanceDisk&) &;
+ GceInstanceInfo AddDisk(const GceInstanceDisk&) &&;
+
+ GceInstanceInfo& AddNetworkInterface(const GceNetworkInterface&) &;
+ GceInstanceInfo AddNetworkInterface(const GceNetworkInterface&) &&;
+ std::vector<GceNetworkInterface> NetworkInterfaces() const;
+
+ GceInstanceInfo& AddMetadata(const std::string&, const std::string&) &;
+ GceInstanceInfo AddMetadata(const std::string&, const std::string&) &&;
+
+ GceInstanceInfo& AddScope(const std::string&) &;
+ GceInstanceInfo AddScope(const std::string&) &&;
+
+ const Json::Value& AsJson() const;
+
+ private:
+ Json::Value data_;
+};
+
+class GceApi {
+ public:
+ class Operation {
+ public:
+ ~Operation();
+ void StopWaiting();
+ /// `true` means it waited to completion, `false` means it was cancelled
+ std::future<android::base::Result<bool>>& Future();
+
+ private:
+ class Impl;
+ std::unique_ptr<Impl> impl_;
+ Operation(std::unique_ptr<Impl>);
+ friend class GceApi;
+ };
+
+ GceApi(CurlWrapper&, CredentialSource& credentials,
+ const std::string& project);
+
+ std::future<android::base::Result<GceInstanceInfo>> Get(
+ const GceInstanceInfo&);
+ std::future<android::base::Result<GceInstanceInfo>> Get(
+ const std::string& zone, const std::string& name);
+
+ Operation Insert(const Json::Value&);
+ Operation Insert(const GceInstanceInfo&);
+
+ Operation Reset(const std::string& zone, const std::string& name);
+ Operation Reset(const GceInstanceInfo&);
+
+ Operation Delete(const std::string& zone, const std::string& name);
+ Operation Delete(const GceInstanceInfo&);
+
+ private:
+ std::vector<std::string> Headers();
+
+ CurlWrapper& curl_;
+ CredentialSource& credentials_;
+ std::string project_;
+};
+
+} // namespace cuttlefish
diff --git a/host/commands/test_gce_driver/key_pair.cpp b/host/commands/test_gce_driver/key_pair.cpp
new file mode 100644
index 0000000..5435f50
--- /dev/null
+++ b/host/commands/test_gce_driver/key_pair.cpp
@@ -0,0 +1,160 @@
+//
+// Copyright (C) 2021 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "host/commands/test_gce_driver/key_pair.h"
+
+#include <openssl/bio.h>
+#include <openssl/evp.h>
+#include <openssl/pem.h>
+#include <openssl/rsa.h>
+
+#include <memory>
+#include <string>
+
+#include <android-base/logging.h>
+#include <android-base/result.h>
+
+#include "common/libs/utils/subprocess.h"
+
+using android::base::Error;
+using android::base::Result;
+
+namespace cuttlefish {
+
+static int SslRecordErrCallback(const char* str, size_t len, void* data) {
+ *reinterpret_cast<std::string*>(data) = std::string(str, len);
+ return 1; // success
+}
+
+class BoringSslKeyPair : public KeyPair {
+ public:
+ /*
+ * We interact with boringssl directly here to avoid ssh-keygen writing
+ * directly to the filesystem. The relevant ssh-keygen command here is
+ *
+ * $ ssh-keygen -t rsa -N "" -f ${TARGET}
+ *
+ * which unfortunately tries to write to `${TARGET}.pub`, making it hard to
+ * use something like /dev/stdout or /proc/self/fd/1 to get the keys.
+ */
+ static Result<std::unique_ptr<KeyPair>> CreateRsa(size_t bytes) {
+ std::unique_ptr<EVP_PKEY_CTX, void (*)(EVP_PKEY_CTX*)> ctx{
+ EVP_PKEY_CTX_new_id(EVP_PKEY_RSA, NULL), EVP_PKEY_CTX_free};
+ std::string error;
+ if (!ctx) {
+ ERR_print_errors_cb(SslRecordErrCallback, &error);
+ return Error() << "EVP_PKEY_CTX_new_id failed: " << error;
+ }
+ if (EVP_PKEY_keygen_init(ctx.get()) <= 0) {
+ ERR_print_errors_cb(SslRecordErrCallback, &error);
+ return Error() << "EVP_PKEY_keygen_init failed: " << error;
+ }
+ if (EVP_PKEY_CTX_set_rsa_keygen_bits(ctx.get(), bytes) <= 0) {
+ ERR_print_errors_cb(SslRecordErrCallback, &error);
+ return Error() << "EVP_PKEY_CTX_set_rsa_keygen_bits failed: " << error;
+ }
+
+ EVP_PKEY* pkey = nullptr;
+ if (EVP_PKEY_keygen(ctx.get(), &pkey) <= 0) {
+ ERR_print_errors_cb(SslRecordErrCallback, &error);
+ return Error() << "EVP_PKEY_keygen failed: " << error;
+ }
+ return std::unique_ptr<KeyPair>{new BoringSslKeyPair(pkey)};
+ }
+
+ Result<std::string> PemPrivateKey() const override {
+ std::unique_ptr<BIO, int (*)(BIO*)> bo(BIO_new(BIO_s_mem()), BIO_free);
+ std::string error;
+ if (!bo) {
+ ERR_print_errors_cb(SslRecordErrCallback, &error);
+ return Error() << "BIO_new failed: " << error;
+ }
+ if (!PEM_write_bio_PrivateKey(bo.get(), pkey_.get(), NULL, NULL, 0, 0,
+ NULL)) {
+ ERR_print_errors_cb(SslRecordErrCallback, &error);
+ return Error() << "PEM_write_bio_PrivateKey failed: " << error;
+ }
+ std::string priv(BIO_pending(bo.get()), ' ');
+ auto written = BIO_read(bo.get(), priv.data(), priv.size());
+ if (written != priv.size()) {
+ return Error() << "Unexpected amount of data written: " << written
+ << " != " << priv.size();
+ }
+ return priv;
+ }
+
+ Result<std::string> PemPublicKey() const override {
+ std::unique_ptr<BIO, int (*)(BIO*)> bo(BIO_new(BIO_s_mem()), BIO_free);
+ std::string error;
+ if (!bo) {
+ ERR_print_errors_cb(SslRecordErrCallback, &error);
+ return Error() << "BIO_new failed: " << error;
+ }
+ if (!PEM_write_bio_PUBKEY(bo.get(), pkey_.get())) {
+ ERR_print_errors_cb(SslRecordErrCallback, &error);
+ return Error() << "PEM_write_bio_PUBKEY failed: " << error;
+ }
+
+ std::string priv(BIO_pending(bo.get()), ' ');
+ auto written = BIO_read(bo.get(), priv.data(), priv.size());
+ if (written != priv.size()) {
+ return Error() << "Unexpected amount of data written: " << written
+ << " != " << priv.size();
+ }
+ return priv;
+ }
+
+ /*
+ * OpenSSH has its own distinct format for public keys, which cannot be
+ * produced directly with OpenSSL/BoringSSL primitives. Luckily it is possible
+ * to convert the BoringSSL-generated RSA key without touching the filesystem.
+ */
+ Result<std::string> OpenSshPublicKey() const override {
+ auto pem_pubkey = PemPublicKey();
+ if (!pem_pubkey.ok()) {
+ return Error() << "Failed to get pem public key: " << pem_pubkey.error();
+ }
+ auto fd = SharedFD::MemfdCreateWithData("", *pem_pubkey);
+ if (!fd->IsOpen()) {
+ return Error() << "Could not create pubkey memfd: " << fd->StrError();
+ }
+ Command cmd("/usr/bin/ssh-keygen");
+ cmd.AddParameter("-i");
+ cmd.AddParameter("-f");
+ cmd.AddParameter("/proc/self/fd/0");
+ cmd.RedirectStdIO(Subprocess::StdIOChannel::kStdIn, fd);
+ cmd.AddParameter("-m");
+ cmd.AddParameter("PKCS8");
+ std::string out;
+ std::string err;
+ auto ret = RunWithManagedStdio(std::move(cmd), nullptr, &out, &err);
+ if (ret != 0) {
+ return Error() << "Could not convert pem key to openssh key. "
+ << "stdout=\"" << out << "\", stderr=\"" << err << "\"";
+ }
+ return out;
+ }
+
+ private:
+ BoringSslKeyPair(EVP_PKEY* pkey) : pkey_(pkey, EVP_PKEY_free) {}
+
+ std::unique_ptr<EVP_PKEY, void (*)(EVP_PKEY*)> pkey_;
+};
+
+Result<std::unique_ptr<KeyPair>> KeyPair::CreateRsa(size_t bytes) {
+ return BoringSslKeyPair::CreateRsa(bytes);
+}
+
+} // namespace cuttlefish
diff --git a/host/commands/test_gce_driver/key_pair.h b/host/commands/test_gce_driver/key_pair.h
new file mode 100644
index 0000000..570a7e6
--- /dev/null
+++ b/host/commands/test_gce_driver/key_pair.h
@@ -0,0 +1,36 @@
+//
+// Copyright (C) 2021 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#pragma once
+
+#include <memory>
+#include <string>
+
+#include <android-base/result.h>
+
+namespace cuttlefish {
+
+struct KeyPair {
+ public:
+ static android::base::Result<std::unique_ptr<KeyPair>> CreateRsa(
+ size_t bytes);
+ virtual ~KeyPair() = default;
+
+ virtual android::base::Result<std::string> PemPrivateKey() const = 0;
+ virtual android::base::Result<std::string> PemPublicKey() const = 0;
+ virtual android::base::Result<std::string> OpenSshPublicKey() const = 0;
+};
+
+}; // namespace cuttlefish
diff --git a/host/commands/test_gce_driver/scoped_instance.cpp b/host/commands/test_gce_driver/scoped_instance.cpp
new file mode 100644
index 0000000..3c13d91
--- /dev/null
+++ b/host/commands/test_gce_driver/scoped_instance.cpp
@@ -0,0 +1,225 @@
+//
+// Copyright (C) 2022 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "host/commands/test_gce_driver/scoped_instance.h"
+
+#include <netinet/ip.h>
+
+#include <random>
+#include <sstream>
+#include <string>
+
+#include <android-base/file.h>
+#include <android-base/result.h>
+
+#include "common/libs/fs/shared_buf.h"
+
+using android::base::Error;
+using android::base::Result;
+
+namespace cuttlefish {
+
+SshCommand& SshCommand::PrivKey(const std::string& privkey_path) & {
+ privkey_path_ = privkey_path;
+ return *this;
+}
+SshCommand SshCommand::PrivKey(const std::string& privkey_path) && {
+ privkey_path_ = privkey_path;
+ return *this;
+}
+
+SshCommand& SshCommand::WithoutKnownHosts() & {
+ without_known_hosts_ = true;
+ return *this;
+}
+SshCommand SshCommand::WithoutKnownHosts() && {
+ without_known_hosts_ = true;
+ return *this;
+}
+
+SshCommand& SshCommand::Username(const std::string& username) & {
+ username_ = username;
+ return *this;
+}
+SshCommand SshCommand::Username(const std::string& username) && {
+ username_ = username;
+ return *this;
+}
+
+SshCommand& SshCommand::Host(const std::string& host) & {
+ host_ = host;
+ return *this;
+}
+SshCommand SshCommand::Host(const std::string& host) && {
+ host_ = host;
+ return *this;
+}
+
+SshCommand& SshCommand::RemotePortForward(uint16_t remote, uint16_t local) & {
+ remote_port_forwards_.push_back({remote, local});
+ return *this;
+}
+SshCommand SshCommand::RemotePortForward(uint16_t remote, uint16_t local) && {
+ remote_port_forwards_.push_back({remote, local});
+ return *this;
+}
+
+SshCommand& SshCommand::RemoteParameter(const std::string& param) & {
+ parameters_.push_back(param);
+ return *this;
+}
+SshCommand SshCommand::RemoteParameter(const std::string& param) && {
+ parameters_.push_back(param);
+ return *this;
+}
+
+Command SshCommand::Build() const {
+ Command remote_cmd{"/usr/bin/ssh"};
+ if (privkey_path_) {
+ remote_cmd.AddParameter("-i");
+ remote_cmd.AddParameter(*privkey_path_);
+ }
+ if (without_known_hosts_) {
+ remote_cmd.AddParameter("-o");
+ remote_cmd.AddParameter("StrictHostKeyChecking=no");
+ remote_cmd.AddParameter("-o");
+ remote_cmd.AddParameter("UserKnownHostsFile=/dev/null");
+ }
+ for (const auto& fwd : remote_port_forwards_) {
+ remote_cmd.AddParameter("-R");
+ remote_cmd.AddParameter(fwd.remote_port, ":127.0.0.1:", fwd.local_port);
+ }
+ if (host_) {
+ remote_cmd.AddParameter(username_ ? *username_ + "@" : "", *host_);
+ }
+ for (const auto& param : parameters_) {
+ remote_cmd.AddParameter(param);
+ }
+ return remote_cmd;
+}
+
+Result<std::unique_ptr<ScopedGceInstance>> ScopedGceInstance::CreateDefault(
+ GceApi& gce, const std::string& zone, const std::string& instance_name,
+ bool internal) {
+ auto ssh_key = KeyPair::CreateRsa(4096);
+ if (!ssh_key.ok()) {
+ return Error() << "Could not create ssh key pair: " << ssh_key.error();
+ }
+
+ auto ssh_pubkey = (*ssh_key)->OpenSshPublicKey();
+ if (!ssh_pubkey.ok()) {
+ return Error() << "Could get openssh format key: " << ssh_pubkey.error();
+ }
+
+ auto default_instance_info =
+ GceInstanceInfo()
+ .Name(instance_name)
+ .Zone(zone)
+ .MachineType("zones/us-west1-a/machineTypes/n1-standard-4")
+ .AddMetadata("ssh-keys", "vsoc-01:" + *ssh_pubkey)
+ .AddNetworkInterface(GceNetworkInterface::Default())
+ .AddDisk(
+ GceInstanceDisk::EphemeralBootDisk()
+ .SourceImage(
+ "projects/cloud-android-releases/global/images/family/"
+ "cuttlefish-google")
+ .SizeGb(30))
+ .AddScope("https://www.googleapis.com/auth/androidbuild.internal")
+ .AddScope("https://www.googleapis.com/auth/devstorage.read_only")
+ .AddScope("https://www.googleapis.com/auth/logging.write");
+
+ auto creation = gce.Insert(default_instance_info).Future().get();
+ if (!creation.ok()) {
+ return Error() << "Failed to create instance: " << creation.error();
+ }
+
+ auto privkey = CF_EXPECT((*ssh_key)->PemPrivateKey());
+ std::unique_ptr<TemporaryFile> privkey_file(CF_EXPECT(new TemporaryFile()));
+ auto fd_dup = SharedFD::Dup(privkey_file->fd);
+ CF_EXPECT(fd_dup->IsOpen());
+ CF_EXPECT(WriteAll(fd_dup, privkey) == privkey.size());
+ fd_dup->Close();
+
+ std::unique_ptr<ScopedGceInstance> instance(new ScopedGceInstance(
+ gce, default_instance_info, std::move(privkey_file), internal));
+
+ auto created_info = gce.Get(default_instance_info).get();
+ if (!created_info.ok()) {
+ return Error() << "Failed to get instance info: " << created_info.error();
+ }
+ instance->instance_ = *created_info;
+
+ auto ssh_ready = instance->EnforceSshReady();
+ if (!ssh_ready.ok()) {
+ return Error() << "Failed to access SSH on instance: " << ssh_ready.error();
+ }
+ return instance;
+}
+
+Result<void> ScopedGceInstance::EnforceSshReady() {
+ std::string out;
+ std::string err;
+ for (int i = 0; i < 100; i++) {
+ auto ssh = Ssh();
+ if (!ssh.ok()) {
+ return Error() << "Failed to create ssh command: " << ssh.error();
+ }
+
+ ssh->RemoteParameter("ls");
+ ssh->RemoteParameter("/");
+ auto command = ssh->Build();
+
+ out = "";
+ err = "";
+ int ret = RunWithManagedStdio(std::move(command), nullptr, &out, &err);
+ if (ret == 0) {
+ return {};
+ }
+ }
+
+ return Error() << "Failed to ssh to the instance. stdout=\"" << out
+ << "\", stderr = \"" << err << "\"";
+}
+
+ScopedGceInstance::ScopedGceInstance(GceApi& gce,
+ const GceInstanceInfo& instance,
+ std::unique_ptr<TemporaryFile> privkey,
+ bool use_internal_address)
+ : gce_(gce),
+ instance_(instance),
+ privkey_(std::move(privkey)),
+ use_internal_address_(use_internal_address) {}
+
+ScopedGceInstance::~ScopedGceInstance() {
+ auto delete_ins = gce_.Delete(instance_).Future().get();
+ if (!delete_ins.ok()) {
+ LOG(ERROR) << "Failed to delete instance: " << delete_ins.error();
+ }
+}
+
+Result<SshCommand> ScopedGceInstance::Ssh() {
+ const auto& network_interfaces = instance_.NetworkInterfaces();
+ CF_EXPECT(!network_interfaces.empty());
+ auto iface = network_interfaces[0];
+ auto ip = use_internal_address_ ? iface.InternalIp() : iface.ExternalIp();
+ CF_EXPECT(ip.has_value());
+ return SshCommand()
+ .PrivKey(privkey_->path)
+ .WithoutKnownHosts()
+ .Username("vsoc-01")
+ .Host(*ip);
+}
+
+} // namespace cuttlefish
diff --git a/host/commands/test_gce_driver/scoped_instance.h b/host/commands/test_gce_driver/scoped_instance.h
new file mode 100644
index 0000000..f4ee87f
--- /dev/null
+++ b/host/commands/test_gce_driver/scoped_instance.h
@@ -0,0 +1,94 @@
+//
+// Copyright (C) 2022 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#pragma once
+
+#include <memory>
+#include <optional>
+#include <string>
+#include <vector>
+
+#include <android-base/file.h>
+#include <android-base/result.h>
+
+#include "common/libs/utils/subprocess.h"
+#include "host/commands/test_gce_driver/gce_api.h"
+#include "host/commands/test_gce_driver/key_pair.h"
+
+namespace cuttlefish {
+
+// TODO(schuffelen): Implement this with libssh2
+class SshCommand {
+ public:
+ SshCommand() = default;
+
+ SshCommand& PrivKey(const std::string& path) &;
+ SshCommand PrivKey(const std::string& path) &&;
+
+ SshCommand& WithoutKnownHosts() &;
+ SshCommand WithoutKnownHosts() &&;
+
+ SshCommand& Username(const std::string& username) &;
+ SshCommand Username(const std::string& username) &&;
+
+ SshCommand& Host(const std::string& host) &;
+ SshCommand Host(const std::string& host) &&;
+
+ SshCommand& RemotePortForward(uint16_t remote, uint16_t local) &;
+ SshCommand RemotePortForward(uint16_t remote, uint16_t local) &&;
+
+ SshCommand& RemoteParameter(const std::string& param) &;
+ SshCommand RemoteParameter(const std::string& param) &&;
+
+ Command Build() const;
+
+ private:
+ struct RemotePortForwardType {
+ uint16_t remote_port;
+ uint16_t local_port;
+ };
+
+ std::optional<std::string> privkey_path_;
+ bool without_known_hosts_;
+ std::optional<std::string> username_;
+ std::optional<std::string> host_;
+ std::vector<RemotePortForwardType> remote_port_forwards_;
+ std::vector<std::string> parameters_;
+};
+
+class ScopedGceInstance {
+ public:
+ static android::base::Result<std::unique_ptr<ScopedGceInstance>>
+ CreateDefault(GceApi& gce, const std::string& zone,
+ const std::string& instance_name, bool internal_addresses);
+ ~ScopedGceInstance();
+
+ android::base::Result<SshCommand> Ssh();
+ android::base::Result<void> Reset();
+
+ private:
+ ScopedGceInstance(GceApi& gce, const GceInstanceInfo& instance,
+ std::unique_ptr<TemporaryFile> privkey,
+ bool internal_addresses);
+
+ android::base::Result<void> EnforceSshReady();
+
+ GceApi& gce_;
+ GceInstanceInfo instance_;
+ std::unique_ptr<TemporaryFile> privkey_;
+ bool use_internal_address_;
+};
+
+} // namespace cuttlefish
diff --git a/host/commands/test_gce_driver/test_gce_driver.proto b/host/commands/test_gce_driver/test_gce_driver.proto
new file mode 100644
index 0000000..128d0fd
--- /dev/null
+++ b/host/commands/test_gce_driver/test_gce_driver.proto
@@ -0,0 +1,88 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+syntax = "proto3";
+
+package cuttlefish.test_gce_driver;
+
+option java_multiple_files = true;
+option java_package = "com.android.cuttlefish.test";
+option java_outer_classname = "TestGceDriverProtos";
+
+message TestMessage {
+ oneof contents {
+ Exit exit = 1;
+ StreamEnd stream_end = 2;
+ Error error = 3;
+ CreateInstance create_instance = 4;
+ SshCommand ssh_command = 5;
+ Data data = 6;
+ UploadBuildArtifact upload_build_artifact = 7;
+ UploadFile upload_file = 8;
+ }
+}
+
+message Exit {}
+
+message StreamEnd {}
+
+message Error {
+ string text = 1;
+}
+
+message GceInstanceId {
+ string name = 1;
+ string zone = 2;
+}
+
+message CreateInstance {
+ GceInstanceId id = 1;
+}
+
+message SshCommand {
+ GceInstanceId instance = 1;
+ repeated string arguments = 2;
+}
+
+enum DataType {
+ DATA_TYPE_UNSPECIFIED = 0;
+ DATA_TYPE_STDOUT = 1;
+ DATA_TYPE_STDERR = 2;
+ DATA_TYPE_RETURN_CODE = 3;
+ DATA_TYPE_FILE_CONTENTS = 4;
+}
+
+message Data {
+ DataType type = 1;
+ bytes contents = 2;
+}
+
+message Build {
+ string id = 1;
+ string target = 2;
+}
+
+message UploadBuildArtifact {
+ GceInstanceId instance = 1;
+ Build build = 2;
+ string artifact_name = 3;
+ string remote_path = 4;
+}
+
+message UploadFile {
+ GceInstanceId instance = 1;
+ string remote_path = 2;
+}
diff --git a/host/commands/tombstone_receiver/Android.bp b/host/commands/tombstone_receiver/Android.bp
index 51830fc..b50cde4 100644
--- a/host/commands/tombstone_receiver/Android.bp
+++ b/host/commands/tombstone_receiver/Android.bp
@@ -23,6 +23,7 @@
"main.cpp",
],
shared_libs: [
+ "libext2_blkid",
"libbase",
"libcuttlefish_fs",
"libjsoncpp",
diff --git a/host/commands/tombstone_receiver/main.cpp b/host/commands/tombstone_receiver/main.cpp
index 2e0ea69..5fa495d 100644
--- a/host/commands/tombstone_receiver/main.cpp
+++ b/host/commands/tombstone_receiver/main.cpp
@@ -22,23 +22,21 @@
#include <iomanip>
#include <sstream>
-#include "host/libs/config/logging.h"
#include "common/libs/fs/shared_fd.h"
+#include "common/libs/utils/flag_parser.h"
+#include "common/libs/utils/shared_fd_flag.h"
+#include "host/libs/config/logging.h"
-DEFINE_int32(
- server_fd, -1,
- "File descriptor to an already created vsock server. If negative a new "
- "server will be created at the port specified on the config file");
-DEFINE_string(tombstone_dir, "", "directory to write out tombstones in");
+namespace cuttlefish {
static uint num_tombstones_in_last_second = 0;
static std::string last_tombstone_name = "";
-static std::string next_tombstone_path() {
+static std::string next_tombstone_path(const std::string& tombstone_dir) {
auto in_time_t = std::chrono::system_clock::to_time_t(std::chrono::system_clock::now());
std::stringstream ss;
- ss << FLAGS_tombstone_dir << "/tombstone_" <<
- std::put_time(std::gmtime(&in_time_t), "%Y-%m-%d-%H%M%S");
+ ss << tombstone_dir << "/tombstone_"
+ << std::put_time(std::gmtime(&in_time_t), "%Y-%m-%d-%H%M%S");
auto retval = ss.str();
// Gives tombstones unique names
@@ -54,23 +52,38 @@
return retval;
}
-#define CHUNK_RECV_MAX_LEN (1024)
-int main(int argc, char** argv) {
- cuttlefish::DefaultSubprocessLogging(argv);
- google::ParseCommandLineFlags(&argc, &argv, true);
+static constexpr size_t CHUNK_RECV_MAX_LEN = 1024;
- cuttlefish::SharedFD server_fd = cuttlefish::SharedFD::Dup(FLAGS_server_fd);
- close(FLAGS_server_fd);
+int TombstoneReceiverMain(int argc, char** argv) {
+ DefaultSubprocessLogging(argv);
- CHECK(server_fd->IsOpen()) << "Error inheriting tombstone server: "
- << server_fd->StrError();
+ std::vector<Flag> flags;
+
+ std::string tombstone_dir;
+ flags.emplace_back(GflagsCompatFlag("tombstone_dir", tombstone_dir)
+ .Help("directory to write out tombstones in"));
+
+ SharedFD server_fd;
+ flags.emplace_back(
+ SharedFDFlag("server_fd", server_fd)
+ .Help("File descriptor to an already created vsock server"));
+
+ flags.emplace_back(HelpFlag(flags));
+ flags.emplace_back(UnexpectedArgumentGuard());
+
+ std::vector<std::string> args =
+ ArgsToVec(argc - 1, argv + 1); // Skip argv[0]
+ CHECK(ParseFlags(flags, args)) << "Could not process command line flags.";
+
+ CHECK(server_fd->IsOpen()) << "Did not receive a server fd";
+
LOG(DEBUG) << "Host is starting server on port "
<< server_fd->VsockServerPort();
// Server loop
while (true) {
- auto conn = cuttlefish::SharedFD::Accept(*server_fd);
- std::ofstream file(next_tombstone_path(),
+ auto conn = SharedFD::Accept(*server_fd);
+ std::ofstream file(next_tombstone_path(tombstone_dir),
std::ofstream::out | std::ofstream::binary);
while (file.is_open()) {
@@ -87,3 +100,9 @@
return 0;
}
+
+} // namespace cuttlefish
+
+int main(int argc, char** argv) {
+ return cuttlefish::TombstoneReceiverMain(argc, argv);
+}
diff --git a/host/commands/mk_cdisk/Android.bp b/host/commands/wmediumd_control/Android.bp
similarity index 78%
rename from host/commands/mk_cdisk/Android.bp
rename to host/commands/wmediumd_control/Android.bp
index a0cf8ba..0e58ec3 100644
--- a/host/commands/mk_cdisk/Android.bp
+++ b/host/commands/wmediumd_control/Android.bp
@@ -18,24 +18,26 @@
}
cc_binary {
- name: "mk_cdisk",
+ name: "wmediumd_control",
srcs: [
- "mk_cdisk.cc",
+ "main.cpp",
],
shared_libs: [
+ "libext2_blkid",
"libcuttlefish_fs",
"libcuttlefish_utils",
"libbase",
+ "libfruit",
"libjsoncpp",
- "liblog",
"libz",
],
static_libs: [
- "libcdisk_spec",
- "libext2_uuid",
- "libimage_aggregator",
- "libprotobuf-cpp-lite",
- "libsparse",
+ "libcuttlefish_host_config",
+ "libcuttlefish_wmediumd_controller",
+ "libgflags",
+ ],
+ header_libs: [
+ "wmediumd_headers",
],
defaults: ["cuttlefish_host"],
}
diff --git a/host/commands/wmediumd_control/main.cpp b/host/commands/wmediumd_control/main.cpp
new file mode 100644
index 0000000..63763b5
--- /dev/null
+++ b/host/commands/wmediumd_control/main.cpp
@@ -0,0 +1,263 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <android-base/logging.h>
+#include <android-base/parseint.h>
+#include <gflags/gflags.h>
+
+#include <cstdlib>
+#include <functional>
+#include <iomanip>
+#include <iostream>
+#include <string>
+#include <unordered_map>
+#include <vector>
+
+#include "host/libs/config/cuttlefish_config.h"
+#include "host/libs/wmediumd_controller/wmediumd_controller.h"
+
+const std::string usageMessage =
+ "wmediumd control commandline utility\n\n"
+ " Usage: wmediumd_control [option] command [args...]\n\n"
+ " Commands:\n\n"
+ " set_snr mac1 mac2 snr\n"
+ " set SNR between two nodes. (0 <= snr <= 255)\n\n"
+ " reload_config [path]\n"
+ " force reload wmediumd configuration file\n\n"
+ " if path is not specified, reload current configuration file\n\n"
+ " start_pcap path\n"
+ " start packet capture and save capture result to file.\n"
+ " file format is pcap capture format.\n\n"
+ " stop_pcap\n"
+ " stop packet capture\n\n"
+ " list_stations\n"
+ " listing stations connected to wmediumd\n\n";
+
+DEFINE_string(wmediumd_api_server, "",
+ "Unix socket path of wmediumd api server");
+
+const int kMacAddrStringSize = 17;
+
+bool ValidMacAddr(const std::string& macAddr) {
+ if (macAddr.size() != kMacAddrStringSize) {
+ return false;
+ }
+
+ if (macAddr[2] != ':' || macAddr[5] != ':' || macAddr[8] != ':' ||
+ macAddr[11] != ':' || macAddr[14] != ':') {
+ return false;
+ }
+
+ for (int i = 0; i < kMacAddrStringSize; ++i) {
+ if ((i - 2) % 3 == 0) continue;
+ char c = macAddr[i];
+
+ if (isupper(c)) {
+ c = tolower(c);
+ }
+
+ if ((c < '0' || c > '9') && (c < 'a' || c > 'f')) return false;
+ }
+
+ return true;
+}
+
+std::string MacToString(const char* macAddr) {
+ std::stringstream result;
+
+ for (int i = 0; i < ETH_ALEN; i++) {
+ result << std::setfill('0') << std::setw(2) << std::right << std::hex
+ << static_cast<int>(static_cast<uint8_t>(macAddr[i]));
+
+ if (i != 5) {
+ result << ":";
+ }
+ }
+
+ return result.str();
+}
+
+bool HandleSetSnrCommand(cuttlefish::WmediumdController& client,
+ const std::vector<std::string>& args) {
+ if (args.size() != 4) {
+ LOG(ERROR) << "error: set_snr must provide 3 options";
+ return false;
+ }
+
+ if (!ValidMacAddr(args[1])) {
+ LOG(ERROR) << "error: invalid mac address " << args[1];
+ return false;
+ }
+
+ if (!ValidMacAddr(args[2])) {
+ LOG(ERROR) << "error: invalid mac address " << args[2];
+ return false;
+ }
+
+ uint8_t snr = 0;
+
+ auto parseResult =
+ android::base::ParseUint<decltype(snr)>(args[3].c_str(), &snr);
+
+ if (!parseResult) {
+ if (errno == EINVAL) {
+ LOG(ERROR) << "error: cannot parse snr: " << args[3];
+ } else if (errno == ERANGE) {
+ LOG(ERROR) << "error: snr exceeded range: " << args[3];
+ }
+
+ return false;
+ }
+
+ if (!client.SetSnr(args[1], args[2], snr)) {
+ return false;
+ }
+
+ return true;
+}
+
+bool HandleReloadConfigCommand(cuttlefish::WmediumdController& client,
+ const std::vector<std::string>& args) {
+ if (args.size() > 2) {
+ LOG(ERROR) << "error: reload_config must provide 0 or 1 option";
+ return false;
+ }
+
+ if (args.size() == 2) {
+ return client.ReloadConfig(args[1]);
+ } else {
+ return client.ReloadCurrentConfig();
+ }
+}
+
+bool HandleStartPcapCommand(cuttlefish::WmediumdController& client,
+ const std::vector<std::string>& args) {
+ if (args.size() != 2) {
+ LOG(ERROR) << "error: you must provide only 1 option(path)";
+ return false;
+ }
+
+ return client.StartPcap(args[1]);
+}
+
+bool HandleStopPcapCommand(cuttlefish::WmediumdController& client,
+ const std::vector<std::string>& args) {
+ if (args.size() != 1) {
+ LOG(ERROR) << "error: you must not provide option";
+ return false;
+ }
+
+ return client.StopPcap();
+}
+
+bool HandleListStationsCommand(cuttlefish::WmediumdController& client,
+ const std::vector<std::string>& args) {
+ if (args.size() != 1) {
+ LOG(ERROR) << "error: you must not provide option";
+ return false;
+ }
+
+ auto result = client.GetStations();
+
+ if (!result) {
+ LOG(ERROR) << "error: failed to get stations";
+ return false;
+ }
+
+ auto stationList = result->GetStations();
+
+ std::cout << "Total stations : " << stationList.size() << std::endl
+ << std::endl;
+ std::cout << "Mac Address "
+ << "\t"
+ << "X Pos"
+ << "\t"
+ << "Y Pos"
+ << "\t"
+ << "TX Power" << std::endl;
+
+ for (auto& station : stationList) {
+ std::cout << MacToString(station.addr) << "\t" << std::setprecision(1)
+ << std::fixed << station.x << "\t" << std::setprecision(1)
+ << std::fixed << station.y << "\t" << station.tx_power
+ << std::endl;
+ }
+
+ std::cout << std::endl;
+
+ return true;
+}
+
+int main(int argc, char** argv) {
+ gflags::SetUsageMessage(usageMessage);
+ gflags::ParseCommandLineFlags(&argc, &argv, true);
+
+ std::vector<std::string> args;
+
+ for (int i = 1; i < argc; ++i) {
+ args.push_back(argv[i]);
+ }
+
+ if (args.size() == 0) {
+ LOG(ERROR) << "error: you must provide at least 1 argument";
+ gflags::ShowUsageWithFlags(argv[0]);
+ return -1;
+ }
+
+ std::string wmediumdApiServerPath(FLAGS_wmediumd_api_server);
+
+ if (wmediumdApiServerPath == "") {
+ const auto cuttlefishConfig = cuttlefish::CuttlefishConfig::Get();
+
+ if (!cuttlefishConfig) {
+ LOG(ERROR) << "error: cannot get global cuttlefish config";
+ return -1;
+ }
+
+ wmediumdApiServerPath = cuttlefishConfig->wmediumd_api_server_socket();
+ }
+
+ auto client = cuttlefish::WmediumdController::New(wmediumdApiServerPath);
+
+ if (!client) {
+ LOG(ERROR) << "error: cannot connect to " << wmediumdApiServerPath;
+ return -1;
+ }
+
+ auto commandMap =
+ std::unordered_map<std::string,
+ std::function<bool(cuttlefish::WmediumdController&,
+ const std::vector<std::string>&)>>{{
+ {"set_snr", HandleSetSnrCommand},
+ {"reload_config", HandleReloadConfigCommand},
+ {"start_pcap", HandleStartPcapCommand},
+ {"stop_pcap", HandleStopPcapCommand},
+ {"list_stations", HandleListStationsCommand},
+ }};
+
+ if (commandMap.find(args[0]) == std::end(commandMap)) {
+ LOG(ERROR) << "error: command " << args[0] << " does not exist";
+ gflags::ShowUsageWithFlags(argv[0]);
+ return -1;
+ }
+
+ if (!commandMap[args[0]](*client, args)) {
+ LOG(ERROR) << "error: failed to execute command " << args[0];
+ return -1;
+ }
+
+ return 0;
+}
diff --git a/host/example_custom_actions/Android.bp b/host/example_custom_actions/Android.bp
index 36161f6..a213099 100644
--- a/host/example_custom_actions/Android.bp
+++ b/host/example_custom_actions/Android.bp
@@ -9,6 +9,7 @@
"cuttlefish_buildhost_only",
],
shared_libs: [
+ "libext2_blkid",
"libbase",
"liblog",
"libutils",
diff --git a/host/example_custom_actions/README.md b/host/example_custom_actions/README.md
index de3764d..44d4fae 100644
--- a/host/example_custom_actions/README.md
+++ b/host/example_custom_actions/README.md
@@ -2,11 +2,8 @@
following build vars:
```
-SOONG_CONFIG_NAMESPACES += cvd
-SOONG_CONFIG_cvd += custom_action_config custom_action_servers
-
-SOONG_CONFIG_cvd_custom_action_config := cuttlefish_example_action_config.json
-SOONG_CONFIG_cvd_custom_action_servers += cuttlefish_example_action_server
+$(call soong_config_set, cvd, custom_action_config, cuttlefish_example_action_config.json)
+$(call soong_config_append, cvd, custom_action_servers, cuttlefish_example_action_server)
```
See `device/google/cuttlefish/build/README.md` for more information.
diff --git a/host/frontend/adb_connector/Android.bp b/host/frontend/adb_connector/Android.bp
index d0927af..d866fd3 100644
--- a/host/frontend/adb_connector/Android.bp
+++ b/host/frontend/adb_connector/Android.bp
@@ -29,6 +29,7 @@
"libjsoncpp",
],
shared_libs: [
+ "libext2_blkid",
"libbase",
"libcuttlefish_fs",
"libcuttlefish_utils",
diff --git a/host/commands/adbshell/Android.bp b/host/frontend/operator_proxy/Android.bp
similarity index 85%
rename from host/commands/adbshell/Android.bp
rename to host/frontend/operator_proxy/Android.bp
index 883503e..7709f59 100644
--- a/host/commands/adbshell/Android.bp
+++ b/host/frontend/operator_proxy/Android.bp
@@ -1,5 +1,5 @@
//
-// Copyright (C) 2018 The Android Open Source Project
+// Copyright (C) 2021 The Android Open Source Project
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
@@ -18,20 +18,22 @@
}
cc_binary_host {
- name: "adbshell",
+ name: "operator_proxy",
srcs: [
"main.cpp",
],
- cflags: [
- "-D_XOPEN_SOURCE",
- ],
shared_libs: [
"libbase",
- "libcuttlefish_utils",
+ "liblog",
+ "libjsoncpp",
+ "libcuttlefish_fs",
],
static_libs: [
+ "libgflags",
+ "libcuttlefish_utils",
"libcuttlefish_host_config",
- "libjsoncpp",
],
defaults: ["cuttlefish_buildhost_only"],
}
+
+
diff --git a/host/frontend/operator_proxy/main.cpp b/host/frontend/operator_proxy/main.cpp
new file mode 100644
index 0000000..d4b640c
--- /dev/null
+++ b/host/frontend/operator_proxy/main.cpp
@@ -0,0 +1,52 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <signal.h>
+
+#include <android-base/logging.h>
+#include <gflags/gflags.h>
+
+#include "common/libs/fs/shared_fd.h"
+#include "common/libs/utils/socket2socket_proxy.h"
+#include "host/libs/config/logging.h"
+
+DEFINE_int32(server_port, 8443, "The port for the proxy server");
+DEFINE_int32(operator_port, 1443, "The port of the operator server to proxy");
+
+cuttlefish::SharedFD OpenConnection() {
+ auto conn =
+ cuttlefish::SharedFD::SocketLocalClient(FLAGS_operator_port, SOCK_STREAM);
+ if (!conn->IsOpen()) {
+ LOG(ERROR) << "Failed to connect to operator: " << conn->StrError();
+ }
+ return conn;
+}
+
+int main(int argc, char** argv) {
+ cuttlefish::DefaultSubprocessLogging(argv);
+ ::gflags::ParseCommandLineFlags(&argc, &argv, true);
+
+ auto server =
+ cuttlefish::SharedFD::SocketLocalServer(FLAGS_server_port, SOCK_STREAM);
+ CHECK(server->IsOpen()) << "Error Creating proxy server: "
+ << server->StrError();
+
+ signal(SIGPIPE, SIG_IGN);
+
+ cuttlefish::Proxy(server, OpenConnection);
+
+ return 0;
+}
diff --git a/host/frontend/vnc_server/Android.bp b/host/frontend/vnc_server/Android.bp
deleted file mode 100644
index 468590b..0000000
--- a/host/frontend/vnc_server/Android.bp
+++ /dev/null
@@ -1,59 +0,0 @@
-//
-// Copyright (C) 2017 The Android Open Source Project
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package {
- default_applicable_licenses: ["Android-Apache-2.0"],
-}
-
-cc_binary {
- name: "vnc_server",
- srcs: [
- "blackboard.cpp",
- "frame_buffer_watcher.cpp",
- "jpeg_compressor.cpp",
- "main.cpp",
- "simulated_hw_composer.cpp",
- "virtual_inputs.cpp",
- "vnc_client_connection.cpp",
- "vnc_server.cpp",
- ],
- shared_libs: [
- "libcuttlefish_fs",
- "libcuttlefish_utils",
- "libbase",
- "libjsoncpp",
- "liblog",
- ],
- header_libs: [
- "libcuttlefish_confui_host_headers",
- ],
- static_libs: [
- "libcuttlefish_host_config",
- "libcuttlefish_screen_connector",
- "libcuttlefish_wayland_server",
- "libcuttlefish_confui",
- "libcuttlefish_confui_host",
- "libft2.nodep",
- "libteeui",
- "libteeui_localization",
- "libffi",
- "libjpeg",
- "libgflags",
- "libwayland_crosvm_gpu_display_extension_server_protocols",
- "libwayland_extension_server_protocols",
- "libwayland_server",
- ],
- defaults: ["cuttlefish_host"],
-}
diff --git a/host/frontend/vnc_server/blackboard.cpp b/host/frontend/vnc_server/blackboard.cpp
deleted file mode 100644
index 91a8d1e..0000000
--- a/host/frontend/vnc_server/blackboard.cpp
+++ /dev/null
@@ -1,167 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#include "host/frontend/vnc_server/blackboard.h"
-
-#include <algorithm>
-#include <utility>
-
-#include <gflags/gflags.h>
-#include <android-base/logging.h>
-#include "host/frontend/vnc_server/frame_buffer_watcher.h"
-
-DEFINE_bool(debug_blackboard, false,
- "Turn on detailed logging for the blackboard");
-
-#define DLOG(LEVEL) \
- if (FLAGS_debug_blackboard) LOG(LEVEL)
-
-using cuttlefish::vnc::BlackBoard;
-using cuttlefish::vnc::Stripe;
-
-cuttlefish::vnc::SeqNumberVec cuttlefish::vnc::MakeSeqNumberVec() {
- return SeqNumberVec(FrameBufferWatcher::StripesPerFrame());
-}
-
-void BlackBoard::NewStripeReady(int index, StripeSeqNumber seq_num) {
- std::lock_guard<std::mutex> guard(m_);
- DLOG(INFO) << "new stripe arrived from frame watcher";
- auto& current_seq_num = most_recent_stripe_seq_nums_[index];
- current_seq_num = std::max(current_seq_num, seq_num);
- for (auto& client : clients_) {
- if (client.second.ready_to_receive) {
- client.second.new_frame_cv.notify_one();
- }
- }
-}
-
-void BlackBoard::Register(const VncClientConnection* conn) {
- {
- std::lock_guard<std::mutex> guard(m_);
- CHECK(!clients_.count(conn));
- clients_[conn]; // constructs new state in place
- }
- new_client_cv_.notify_one();
-}
-
-void BlackBoard::Unregister(const VncClientConnection* conn) {
- std::lock_guard<std::mutex> guard(m_);
- CHECK(clients_.count(conn));
- clients_.erase(clients_.find(conn));
-}
-
-bool BlackBoard::NoNewStripesFor(const SeqNumberVec& seq_nums) const {
- CHECK(seq_nums.size() == most_recent_stripe_seq_nums_.size());
- for (auto state_seq_num = seq_nums.begin(),
- held_seq_num = most_recent_stripe_seq_nums_.begin();
- state_seq_num != seq_nums.end(); ++state_seq_num, ++held_seq_num) {
- if (*state_seq_num < *held_seq_num) {
- return false;
- }
- }
- return true;
-}
-
-cuttlefish::vnc::StripePtrVec BlackBoard::WaitForSenderWork(
- const VncClientConnection* conn) {
- std::unique_lock<std::mutex> guard(m_);
- auto& state = GetStateForClient(conn);
- DLOG(INFO) << "Waiting for stripe...";
- while (!state.closed &&
- (!state.ready_to_receive || NoNewStripesFor(state.stripe_seq_nums))) {
- state.new_frame_cv.wait(guard);
- }
- DLOG(INFO) << "At least one new stripe is available, should unblock " << conn;
- state.ready_to_receive = false;
- auto new_stripes = frame_buffer_watcher_->StripesNewerThan(
- state.orientation, state.stripe_seq_nums);
- for (auto& s : new_stripes) {
- state.stripe_seq_nums[s->index] = s->seq_number;
- }
- return new_stripes;
-}
-
-void BlackBoard::WaitForAtLeastOneClientConnection() {
- std::unique_lock<std::mutex> guard(m_);
- while (clients_.empty()) {
- new_client_cv_.wait(guard);
- }
-}
-
-void BlackBoard::SetOrientation(const VncClientConnection* conn,
- ScreenOrientation orientation) {
- std::lock_guard<std::mutex> guard(m_);
- auto& state = GetStateForClient(conn);
- state.orientation = orientation;
- // After an orientation change the vnc client will need all stripes from
- // the new orientation, regardless of age.
- ResetToZero(&state.stripe_seq_nums);
-}
-
-void BlackBoard::SignalClientNeedsEntireScreen(
- const VncClientConnection* conn) {
- std::lock_guard<std::mutex> guard(m_);
- ResetToZero(&GetStateForClient(conn).stripe_seq_nums);
-}
-
-void BlackBoard::ResetToZero(SeqNumberVec* seq_nums) {
- seq_nums->assign(FrameBufferWatcher::StripesPerFrame(), StripeSeqNumber{});
-}
-
-void BlackBoard::FrameBufferUpdateRequestReceived(
- const VncClientConnection* conn) {
- std::lock_guard<std::mutex> guard(m_);
- DLOG(INFO) << "Received frame buffer update request";
- auto& state = GetStateForClient(conn);
- state.ready_to_receive = true;
- state.new_frame_cv.notify_one();
-}
-
-void BlackBoard::StopWaiting(const VncClientConnection* conn) {
- std::lock_guard<std::mutex> guard(m_);
- auto& state = GetStateForClient(conn);
- state.closed = true;
- // Wake up the thread that might be in WaitForSenderWork()
- state.new_frame_cv.notify_one();
-}
-
-void BlackBoard::set_frame_buffer_watcher(
- cuttlefish::vnc::FrameBufferWatcher* frame_buffer_watcher) {
- std::lock_guard<std::mutex> guard(m_);
- frame_buffer_watcher_ = frame_buffer_watcher;
-}
-
-void BlackBoard::set_jpeg_quality_level(int quality_level) {
- // NOTE all vnc clients share a common jpeg quality level because the
- // server doesn't compress per-client. The quality level for all clients
- // will be whatever the most recent set was by any client.
- std::lock_guard<std::mutex> guard(m_);
- if (quality_level < kJpegMinQualityEncoding ||
- quality_level > kJpegMaxQualityEncoding) {
- LOG(WARNING) << "Bogus jpeg quality level: " << quality_level
- << ". Quality must be in range [" << kJpegMinQualityEncoding
- << ", " << kJpegMaxQualityEncoding << "]";
- return;
- }
- jpeg_quality_level_ = 55 + (5 * (quality_level + 32));
- DLOG(INFO) << "jpeg quality level set to " << jpeg_quality_level_ << "%";
-}
-
-BlackBoard::ClientFBUState& BlackBoard::GetStateForClient(
- const VncClientConnection* conn) {
- CHECK(clients_.count(conn));
- return clients_[conn];
-}
diff --git a/host/frontend/vnc_server/blackboard.h b/host/frontend/vnc_server/blackboard.h
deleted file mode 100644
index af4baea..0000000
--- a/host/frontend/vnc_server/blackboard.h
+++ /dev/null
@@ -1,113 +0,0 @@
-#pragma once
-
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-
-#include <condition_variable>
-#include <memory>
-#include <mutex>
-#include <unordered_map>
-
-#include "common/libs/concurrency/thread_annotations.h"
-#include "host/frontend/vnc_server/vnc_utils.h"
-
-namespace cuttlefish {
-namespace vnc {
-
-class VncClientConnection;
-class FrameBufferWatcher;
-using StripePtrVec = std::vector<std::shared_ptr<const Stripe>>;
-using SeqNumberVec = std::vector<StripeSeqNumber>;
-
-SeqNumberVec MakeSeqNumberVec();
-
-class BlackBoard {
- private:
- struct ClientFBUState {
- bool ready_to_receive{};
- ScreenOrientation orientation{};
- std::condition_variable new_frame_cv;
- SeqNumberVec stripe_seq_nums = MakeSeqNumberVec();
- bool closed{};
- };
-
- public:
- class Registerer {
- public:
- Registerer(BlackBoard* bb, const VncClientConnection* conn)
- : bb_{bb}, conn_{conn} {
- bb->Register(conn);
- }
- ~Registerer() { bb_->Unregister(conn_); }
- Registerer(const Registerer&) = delete;
- Registerer& operator=(const Registerer&) = delete;
-
- private:
- BlackBoard* bb_{};
- const VncClientConnection* conn_{};
- };
-
- BlackBoard() = default;
- BlackBoard(const BlackBoard&) = delete;
- BlackBoard& operator=(const BlackBoard&) = delete;
-
- bool NoNewStripesFor(const SeqNumberVec& seq_nums) const REQUIRES(m_);
- void NewStripeReady(int index, StripeSeqNumber seq_num);
- void Register(const VncClientConnection* conn);
- void Unregister(const VncClientConnection* conn);
-
- StripePtrVec WaitForSenderWork(const VncClientConnection* conn);
-
- void WaitForAtLeastOneClientConnection();
-
- void FrameBufferUpdateRequestReceived(const VncClientConnection* conn);
- // Setting orientation implies needing the entire screen
- void SetOrientation(const VncClientConnection* conn,
- ScreenOrientation orientation);
- void SignalClientNeedsEntireScreen(const VncClientConnection* conn);
-
- void StopWaiting(const VncClientConnection* conn);
-
- void set_frame_buffer_watcher(FrameBufferWatcher* frame_buffer_watcher);
-
- // quality_level must be the value received from the client, in the range
- // [kJpegMinQualityEncoding, kJpegMaxQualityEncoding], else it is ignored.
- void set_jpeg_quality_level(int quality_level);
-
- int jpeg_quality_level() const {
- std::lock_guard<std::mutex> guard(m_);
- return jpeg_quality_level_;
- }
-
- private:
- ClientFBUState& GetStateForClient(const VncClientConnection* conn)
- REQUIRES(m_);
- static void ResetToZero(SeqNumberVec* seq_nums);
-
- mutable std::mutex m_;
- SeqNumberVec most_recent_stripe_seq_nums_ GUARDED_BY(m_) = MakeSeqNumberVec();
- std::unordered_map<const VncClientConnection*, ClientFBUState> clients_
- GUARDED_BY(m_);
- int jpeg_quality_level_ GUARDED_BY(m_) = 100;
- std::condition_variable new_client_cv_;
- // NOTE the FrameBufferWatcher pointer itself should be
- // guarded, but not the pointee.
- FrameBufferWatcher* frame_buffer_watcher_ GUARDED_BY(m_){};
-};
-
-} // namespace vnc
-} // namespace cuttlefish
diff --git a/host/frontend/vnc_server/frame_buffer_watcher.cpp b/host/frontend/vnc_server/frame_buffer_watcher.cpp
deleted file mode 100644
index 3cc39c0..0000000
--- a/host/frontend/vnc_server/frame_buffer_watcher.cpp
+++ /dev/null
@@ -1,200 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#include "host/frontend/vnc_server/frame_buffer_watcher.h"
-
-#include <algorithm>
-#include <cstdint>
-#include <cstring>
-#include <iterator>
-#include <memory>
-#include <mutex>
-#include <thread>
-#include <utility>
-
-#include <android-base/logging.h>
-#include "host/frontend/vnc_server/vnc_utils.h"
-
-using cuttlefish::vnc::FrameBufferWatcher;
-
-FrameBufferWatcher::FrameBufferWatcher(BlackBoard* bb,
- ScreenConnector& screen_connector)
- : bb_{bb}, hwcomposer{bb_, screen_connector} {
- for (auto& stripes_vec : stripes_) {
- std::generate_n(std::back_inserter(stripes_vec),
- SimulatedHWComposer::NumberOfStripes(),
- std::make_shared<Stripe>);
- }
- bb_->set_frame_buffer_watcher(this);
- auto num_workers = std::max(std::thread::hardware_concurrency(), 1u);
- std::generate_n(std::back_inserter(workers_), num_workers, [this] {
- return std::thread{&FrameBufferWatcher::Worker, this};
- });
-}
-
-FrameBufferWatcher::~FrameBufferWatcher() {
- {
- std::lock_guard<std::mutex> guard(m_);
- closed_ = true;
- }
- for (auto& tid : workers_) {
- tid.join();
- }
-}
-
-bool FrameBufferWatcher::closed() const {
- std::lock_guard<std::mutex> guard(m_);
- return closed_;
-}
-
-cuttlefish::vnc::Stripe FrameBufferWatcher::Rotated(Stripe stripe) {
- if (stripe.orientation == ScreenOrientation::Landscape) {
- LOG(FATAL) << "Rotating a landscape stripe, this is a mistake";
- }
- auto w = stripe.width;
- auto s = stripe.stride;
- auto h = stripe.height;
- const auto& raw = stripe.raw_data;
- Message rotated(raw.size(), 0xAA);
- for (std::uint16_t i = 0; i < w; ++i) {
- for (std::uint16_t j = 0; j < h; ++j) {
- size_t to = (i * h + j) * ScreenConnectorInfo::BytesPerPixel();
- size_t from = (w - (i + 1)) * ScreenConnectorInfo::BytesPerPixel() + s * j;
- CHECK(from < raw.size());
- CHECK(to < rotated.size());
- std::memcpy(&rotated[to], &raw[from], ScreenConnectorInfo::BytesPerPixel());
- }
- }
- std::swap(stripe.x, stripe.y);
- std::swap(stripe.width, stripe.height);
- // The new stride after rotating is the height, as it is not aligned again.
- stripe.stride = stripe.width * ScreenConnectorInfo::BytesPerPixel();
- stripe.raw_data = std::move(rotated);
- stripe.orientation = ScreenOrientation::Landscape;
- return stripe;
-}
-
-bool FrameBufferWatcher::StripeIsDifferentFromPrevious(
- const Stripe& stripe) const {
- return Stripes(stripe.orientation)[stripe.index]->raw_data != stripe.raw_data;
-}
-
-cuttlefish::vnc::StripePtrVec FrameBufferWatcher::StripesNewerThan(
- ScreenOrientation orientation, const SeqNumberVec& seq_numbers) const {
- std::lock_guard<std::mutex> guard(stripes_lock_);
- const auto& stripes = Stripes(orientation);
- CHECK(seq_numbers.size() == stripes.size());
- StripePtrVec new_stripes;
- auto seq_number_it = seq_numbers.begin();
- std::copy_if(stripes.begin(), stripes.end(), std::back_inserter(new_stripes),
- [seq_number_it](const StripePtrVec::value_type& s) mutable {
- return *(seq_number_it++) < s->seq_number;
- });
- return new_stripes;
-}
-
-cuttlefish::vnc::StripePtrVec& FrameBufferWatcher::Stripes(
- ScreenOrientation orientation) {
- return stripes_[static_cast<int>(orientation)];
-}
-
-const cuttlefish::vnc::StripePtrVec& FrameBufferWatcher::Stripes(
- ScreenOrientation orientation) const {
- return stripes_[static_cast<int>(orientation)];
-}
-
-bool FrameBufferWatcher::UpdateMostRecentSeqNumIfStripeIsNew(
- const Stripe& stripe) {
- if (most_recent_identical_stripe_seq_nums_[stripe.index] <=
- stripe.seq_number) {
- most_recent_identical_stripe_seq_nums_[stripe.index] = stripe.seq_number;
- return true;
- }
- return false;
-}
-
-bool FrameBufferWatcher::UpdateStripeIfStripeIsNew(
- const std::shared_ptr<const Stripe>& stripe) {
- std::lock_guard<std::mutex> guard(stripes_lock_);
- if (UpdateMostRecentSeqNumIfStripeIsNew(*stripe)) {
- Stripes(stripe->orientation)[stripe->index] = stripe;
- return true;
- }
- return false;
-}
-
-void FrameBufferWatcher::CompressStripe(JpegCompressor* jpeg_compressor,
- Stripe* stripe) {
- stripe->jpeg_data = jpeg_compressor->Compress(
- stripe->raw_data, bb_->jpeg_quality_level(), 0, 0, stripe->width,
- stripe->height, stripe->stride);
-}
-
-void FrameBufferWatcher::Worker() {
- JpegCompressor jpeg_compressor;
-#ifdef FUZZ_TEST_VNC
- std::default_random_engine e{std::random_device{}()};
- std::uniform_int_distribution<int> random{0, 2};
-#endif
- while (!closed()) {
- auto portrait_stripe = hwcomposer.GetNewStripe();
- if (closed()) {
- break;
- }
- {
- // TODO(haining) use if (with init) and else for c++17 instead of extra
- // scope and continue
- // if (std::lock_guard guard(stripes_lock_); /*condition*/) { }
- std::lock_guard<std::mutex> guard(stripes_lock_);
- if (!StripeIsDifferentFromPrevious(portrait_stripe)) {
- UpdateMostRecentSeqNumIfStripeIsNew(portrait_stripe);
- continue;
- }
- }
- auto seq_num = portrait_stripe.seq_number;
- auto index = portrait_stripe.index;
- auto landscape_stripe = Rotated(portrait_stripe);
- auto stripes = {std::make_shared<Stripe>(std::move(portrait_stripe)),
- std::make_shared<Stripe>(std::move(landscape_stripe))};
- for (auto& stripe : stripes) {
-#ifdef FUZZ_TEST_VNC
- if (random(e)) {
- usleep(10000);
- }
-#endif
- CompressStripe(&jpeg_compressor, stripe.get());
- }
- bool any_new_stripes = false;
- for (auto& stripe : stripes) {
- any_new_stripes = UpdateStripeIfStripeIsNew(stripe) || any_new_stripes;
- }
- if (any_new_stripes) {
- bb_->NewStripeReady(index, seq_num);
- }
- }
-}
-
-int FrameBufferWatcher::StripesPerFrame() {
- return SimulatedHWComposer::NumberOfStripes();
-}
-
-void FrameBufferWatcher::IncClientCount() {
- hwcomposer.ReportClientsConnected();
-}
-
-void FrameBufferWatcher::DecClientCount() {
- // Do nothing
-}
diff --git a/host/frontend/vnc_server/frame_buffer_watcher.h b/host/frontend/vnc_server/frame_buffer_watcher.h
deleted file mode 100644
index 9b559b6..0000000
--- a/host/frontend/vnc_server/frame_buffer_watcher.h
+++ /dev/null
@@ -1,81 +0,0 @@
-#pragma once
-
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#include <memory>
-#include <mutex>
-#include <thread>
-#include <utility>
-#include <vector>
-
-#include "common/libs/concurrency/thread_annotations.h"
-#include "host/frontend/vnc_server/blackboard.h"
-#include "host/frontend/vnc_server/jpeg_compressor.h"
-#include "host/frontend/vnc_server/simulated_hw_composer.h"
-#include "host/libs/screen_connector/screen_connector.h"
-
-namespace cuttlefish {
-namespace vnc {
-class FrameBufferWatcher {
- public:
- explicit FrameBufferWatcher(BlackBoard* bb,
- ScreenConnector& screen_connector);
- FrameBufferWatcher(const FrameBufferWatcher&) = delete;
- FrameBufferWatcher& operator=(const FrameBufferWatcher&) = delete;
- ~FrameBufferWatcher();
-
- StripePtrVec StripesNewerThan(ScreenOrientation orientation,
- const SeqNumberVec& seq_num) const;
- void IncClientCount();
- void DecClientCount();
-
- static int StripesPerFrame();
-
- private:
- static Stripe Rotated(Stripe stripe);
-
- bool closed() const;
- bool StripeIsDifferentFromPrevious(const Stripe& stripe) const
- REQUIRES(stripes_lock_);
- // returns true if stripe is still considered new and seq number was updated
- bool UpdateMostRecentSeqNumIfStripeIsNew(const Stripe& stripe)
- REQUIRES(stripes_lock_);
- // returns true if stripe is still considered new and was updated
- bool UpdateStripeIfStripeIsNew(const std::shared_ptr<const Stripe>& stripe)
- EXCLUDES(stripes_lock_);
- // Compresses stripe->raw_data to stripe->jpeg_data
- void CompressStripe(JpegCompressor* jpeg_compressor, Stripe* stripe);
- void Worker();
- void Updater();
-
- StripePtrVec& Stripes(ScreenOrientation orientation) REQUIRES(stripes_lock_);
- const StripePtrVec& Stripes(ScreenOrientation orientation) const
- REQUIRES(stripes_lock_);
-
- std::vector<std::thread> workers_;
- mutable std::mutex stripes_lock_;
- std::array<StripePtrVec, kNumOrientations> stripes_ GUARDED_BY(stripes_lock_);
- SeqNumberVec most_recent_identical_stripe_seq_nums_
- GUARDED_BY(stripes_lock_) = MakeSeqNumberVec();
- mutable std::mutex m_;
- bool closed_ GUARDED_BY(m_){};
- BlackBoard* bb_{};
- SimulatedHWComposer hwcomposer;
-};
-
-} // namespace vnc
-} // namespace cuttlefish
diff --git a/host/frontend/vnc_server/jpeg_compressor.cpp b/host/frontend/vnc_server/jpeg_compressor.cpp
deleted file mode 100644
index 05013eb..0000000
--- a/host/frontend/vnc_server/jpeg_compressor.cpp
+++ /dev/null
@@ -1,77 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#include <stdio.h> // stdio.h must appear before jpeglib.h
-#include <jpeglib.h>
-
-#include <android-base/logging.h>
-#include "host/frontend/vnc_server/jpeg_compressor.h"
-#include "host/frontend/vnc_server/vnc_utils.h"
-#include "host/libs/screen_connector/screen_connector.h"
-
-using cuttlefish::vnc::JpegCompressor;
-
-namespace {
-void InitCinfo(jpeg_compress_struct* cinfo, jpeg_error_mgr* err,
- std::uint16_t width, std::uint16_t height, int jpeg_quality) {
- cinfo->err = jpeg_std_error(err);
- jpeg_create_compress(cinfo);
-
- cinfo->image_width = width;
- cinfo->image_height = height;
- cinfo->input_components = cuttlefish::ScreenConnectorInfo::BytesPerPixel();
- cinfo->in_color_space = JCS_EXT_RGBX;
-
- jpeg_set_defaults(cinfo);
- jpeg_set_quality(cinfo, jpeg_quality, true);
-}
-} // namespace
-
-cuttlefish::Message JpegCompressor::Compress(const Message& frame,
- int jpeg_quality, std::uint16_t x,
- std::uint16_t y, std::uint16_t width,
- std::uint16_t height,
- int stride) {
- jpeg_compress_struct cinfo{};
- jpeg_error_mgr err{};
- InitCinfo(&cinfo, &err, width, height, jpeg_quality);
-
- auto* compression_buffer = buffer_.get();
- auto compression_buffer_size = buffer_capacity_;
- jpeg_mem_dest(&cinfo, &compression_buffer, &compression_buffer_size);
- jpeg_start_compress(&cinfo, true);
-
- while (cinfo.next_scanline < cinfo.image_height) {
- auto row = static_cast<JSAMPROW>(const_cast<std::uint8_t*>(
- &frame[(y * stride) +
- (cinfo.next_scanline * stride) +
- (x * cuttlefish::ScreenConnectorInfo::BytesPerPixel())]));
- jpeg_write_scanlines(&cinfo, &row, 1);
- }
- jpeg_finish_compress(&cinfo);
- jpeg_destroy_compress(&cinfo);
-
- UpdateBuffer(compression_buffer, compression_buffer_size);
- return {compression_buffer, compression_buffer + compression_buffer_size};
-}
-
-void JpegCompressor::UpdateBuffer(std::uint8_t* compression_buffer,
- unsigned long compression_buffer_size) {
- if (buffer_.get() != compression_buffer) {
- buffer_capacity_ = compression_buffer_size;
- buffer_.reset(compression_buffer);
- }
-}
diff --git a/host/frontend/vnc_server/jpeg_compressor.h b/host/frontend/vnc_server/jpeg_compressor.h
deleted file mode 100644
index b6ef487..0000000
--- a/host/frontend/vnc_server/jpeg_compressor.h
+++ /dev/null
@@ -1,52 +0,0 @@
-#pragma once
-
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#include <cstdint>
-#include <cstdlib>
-#include <memory>
-
-#include "host/frontend/vnc_server/vnc_utils.h"
-
-namespace cuttlefish {
-namespace vnc {
-
-// libjpeg-turbo with jpeg_mem_dest (using memory as a destination) is funky.
-// If you give it a buffer that is big enough it will use it.
-// If you give it a buffer that is too small, it will allocate a new buffer
-// but will NOT free the buffer you gave it.
-// This class keeps track of the capacity of the working buffer, and frees the
-// old buffer if libjpeg-turbo silently discards it.
-class JpegCompressor {
- public:
- Message Compress(const Message& frame, int jpeg_quality, std::uint16_t x,
- std::uint16_t y, std::uint16_t width, std::uint16_t height,
- int screen_width);
-
- private:
- void UpdateBuffer(std::uint8_t* compression_buffer,
- unsigned long compression_buffer_size);
- struct Freer {
- void operator()(void* p) const { std::free(p); }
- };
-
- std::unique_ptr<std::uint8_t, Freer> buffer_;
- unsigned long buffer_capacity_{};
-};
-
-} // namespace vnc
-} // namespace cuttlefish
diff --git a/host/frontend/vnc_server/keysyms.h b/host/frontend/vnc_server/keysyms.h
deleted file mode 100644
index ddcc0e4..0000000
--- a/host/frontend/vnc_server/keysyms.h
+++ /dev/null
@@ -1,54 +0,0 @@
-#pragma once
-
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#include <cstdint>
-
-namespace cuttlefish {
-namespace xk {
-
-constexpr uint32_t BackSpace = 0xff08, Tab = 0xff09, Return = 0xff0d,
- Enter = Return, Escape = 0xff1b, MultiKey = 0xff20,
- Insert = 0xff63, Delete = 0xffff, Pause = 0xff13,
- Home = 0xff50, End = 0xff57, PageUp = 0xff55,
- PageDown = 0xff56, Left = 0xff51, Up = 0xff52,
- Right = 0xff53, Down = 0xff54, F1 = 0xffbe, F2 = 0xffbf,
- F3 = 0xffc0, F4 = 0xffc1, F5 = 0xffc2, F6 = 0xffc3,
- F7 = 0xffc4, F8 = 0xffc5, F9 = 0xffc6, F10 = 0xffc7,
- F11 = 0xffc8, F12 = 0xffc9, F13 = 0xffca, F14 = 0xffcb,
- F15 = 0xffcc, F16 = 0xffcd, F17 = 0xffce, F18 = 0xffcf,
- F19 = 0xffd0, F20 = 0xffd1, F21 = 0xffd2, F22 = 0xffd3,
- F23 = 0xffd4, F24 = 0xffd5, ShiftLeft = 0xffe1,
- ShiftRight = 0xffe2, ControlLeft = 0xffe3,
- ControlRight = 0xffe4, MetaLeft = 0xffe7, MetaRight = 0xffe8,
- AltLeft = 0xffe9, AltRight = 0xffea, CapsLock = 0xffe5,
- NumLock = 0xff7f, ScrollLock = 0xff14, Keypad0 = 0xffb0,
- Keypad1 = 0xffb1, Keypad2 = 0xffb2, Keypad3 = 0xffb3,
- Keypad4 = 0xffb4, Keypad5 = 0xffb5, Keypad6 = 0xffb6,
- Keypad7 = 0xffb7, Keypad8 = 0xffb8, Keypad9 = 0xffb9,
- KeypadMultiply = 0xffaa, KeypadSubtract = 0xffad,
- KeypadAdd = 0xffab, KeypadDecimal = 0xffae,
- KeypadEnter = 0xff8d, KeypadDivide = 0xffaf,
- KeypadEqual = 0xffbd, PlusMinus = 0xb1, SysReq = 0xff15,
- LineFeed = 0xff0a, KeypadSeparator = 0xffac, Yen = 0xa5,
- Cancel = 0xff69, Undo = 0xff65, Redo = 0xff66, Find = 0xff68,
- Print = 0xff61, VolumeDown = 0x1008ff11, Mute = 0x1008ff12,
- VolumeUp = 0x1008ff13, Menu = 0xff67,
- VNCMenu = 0xffed; // VNC seems to translate MENU to this
-
-} // namespace xk
-} // namespace cuttlefish
diff --git a/host/frontend/vnc_server/main.cpp b/host/frontend/vnc_server/main.cpp
deleted file mode 100644
index e036b40..0000000
--- a/host/frontend/vnc_server/main.cpp
+++ /dev/null
@@ -1,54 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#include <algorithm>
-#include <memory>
-#include <string>
-
-#include <gflags/gflags.h>
-
-#include "host/frontend/vnc_server/simulated_hw_composer.h"
-#include "host/frontend/vnc_server/vnc_server.h"
-#include "host/frontend/vnc_server/vnc_utils.h"
-#include "host/libs/config/logging.h"
-#include "host/libs/confui/host_mode_ctrl.h"
-#include "host/libs/confui/host_server.h"
-
-DEFINE_bool(agressive, false, "Whether to use agressive server");
-DEFINE_int32(frame_server_fd, -1, "");
-DEFINE_int32(port, 6444, "Port where to listen for connections");
-
-int main(int argc, char* argv[]) {
- cuttlefish::DefaultSubprocessLogging(argv);
- google::ParseCommandLineFlags(&argc, &argv, true);
-
- auto& host_mode_ctrl = cuttlefish::HostModeCtrl::Get();
- auto screen_connector_ptr = cuttlefish::vnc::ScreenConnector::Get(
- FLAGS_frame_server_fd, host_mode_ctrl);
- auto& screen_connector = *(screen_connector_ptr.get());
-
- // create confirmation UI service, giving host_mode_ctrl and
- // screen_connector
- // keep this singleton object alive until the webRTC process ends
- static auto& host_confui_server =
- cuttlefish::confui::HostServer::Get(host_mode_ctrl, screen_connector);
-
- host_confui_server.Start();
- // lint does not like the spelling of "agressive", so needs NOTYPO
- cuttlefish::vnc::VncServer vnc_server(FLAGS_port, FLAGS_agressive, // NOTYPO
- screen_connector, host_confui_server);
- vnc_server.MainLoop();
-}
diff --git a/host/frontend/vnc_server/mocks.h b/host/frontend/vnc_server/mocks.h
deleted file mode 100644
index e69eb43..0000000
--- a/host/frontend/vnc_server/mocks.h
+++ /dev/null
@@ -1,35 +0,0 @@
-#pragma once
-
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-struct GceFrameBuffer {
- typedef uint32_t Pixel;
-
- static const int kRedShift = 0;
- static const int kRedBits = 8;
- static const int kGreenShift = 8;
- static const int kGreenBits = 8;
- static const int kBlueShift = 16;
- static const int kBlueBits = 8;
- static const int kAlphaShift = 24;
- static const int kAlphaBits = 8;
-};
-
-// Sensors
-struct gce_sensors_message {
- static constexpr const char* const kSensorsHALSocketName = "";
-};
diff --git a/host/frontend/vnc_server/simulated_hw_composer.cpp b/host/frontend/vnc_server/simulated_hw_composer.cpp
deleted file mode 100644
index a02ae4b..0000000
--- a/host/frontend/vnc_server/simulated_hw_composer.cpp
+++ /dev/null
@@ -1,176 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#include "host/frontend/vnc_server/simulated_hw_composer.h"
-
-#include "host/frontend/vnc_server/vnc_utils.h"
-#include "host/libs/config/cuttlefish_config.h"
-
-using cuttlefish::vnc::SimulatedHWComposer;
-using ScreenConnector = cuttlefish::vnc::ScreenConnector;
-
-SimulatedHWComposer::SimulatedHWComposer(BlackBoard* bb,
- ScreenConnector& screen_connector)
- :
-#ifdef FUZZ_TEST_VNC
- engine_{std::random_device{}()},
-#endif
- bb_{bb},
- stripes_(kMaxQueueElements, &SimulatedHWComposer::EraseHalfOfElements),
- screen_connector_(screen_connector) {
- stripe_maker_ = std::thread(&SimulatedHWComposer::MakeStripes, this);
- screen_connector_.SetCallback(std::move(GetScreenConnectorCallback()));
-}
-
-SimulatedHWComposer::~SimulatedHWComposer() {
- close();
- stripe_maker_.join();
-}
-
-cuttlefish::vnc::Stripe SimulatedHWComposer::GetNewStripe() {
- auto s = stripes_.Pop();
-#ifdef FUZZ_TEST_VNC
- if (random_(engine_)) {
- usleep(7000);
- stripes_.Push(std::move(s));
- s = stripes_.Pop();
- }
-#endif
- return s;
-}
-
-bool SimulatedHWComposer::closed() {
- std::lock_guard<std::mutex> guard(m_);
- return closed_;
-}
-
-void SimulatedHWComposer::close() {
- std::lock_guard<std::mutex> guard(m_);
- closed_ = true;
-}
-
-// Assuming the number of stripes is less than half the size of the queue
-// this will be safe as the newest stripes won't be lost. In the real
-// hwcomposer, where stripes are coming in a different order, the full
-// queue case would probably need a different approach to be safe.
-void SimulatedHWComposer::EraseHalfOfElements(
- ThreadSafeQueue<Stripe>::QueueImpl* q) {
- q->erase(q->begin(), std::next(q->begin(), kMaxQueueElements / 2));
-}
-
-SimulatedHWComposer::GenerateProcessedFrameCallback
-SimulatedHWComposer::GetScreenConnectorCallback() {
- return [](std::uint32_t display_number, std::uint32_t frame_w,
- std::uint32_t frame_h, std::uint32_t frame_stride_bytes,
- std::uint8_t* frame_bytes,
- cuttlefish::vnc::VncScProcessedFrame& processed_frame) {
- processed_frame.display_number_ = display_number;
- // TODO(171305898): handle multiple displays.
- if (display_number != 0) {
- // BUG 186580833: display_number comes from surface_id in crosvm
- // create_surface from virtio_gpu.rs set_scanout. We cannot use it as
- // the display number. Either crosvm virtio-gpu is incorrectly ignoring
- // scanout id and instead using a monotonically increasing surface id
- // number as the scanout resource is replaced over time, or frontend code
- // here is incorrectly assuming surface id == display id.
- display_number = 0;
- }
-
- const std::uint32_t frame_bpp = 4;
- const std::uint32_t frame_size_bytes = frame_h * frame_stride_bytes;
-
- auto& raw_screen = processed_frame.raw_screen_;
- raw_screen.assign(frame_bytes, frame_bytes + frame_size_bytes);
-
- static std::uint32_t next_frame_number = 0;
-
- const auto num_stripes = SimulatedHWComposer::kNumStripes;
- for (int i = 0; i < num_stripes; ++i) {
- std::uint16_t y = (frame_h / num_stripes) * i;
-
- // Last frames on the right and/or bottom handle extra pixels
- // when a screen dimension is not evenly divisible by Frame::kNumSlots.
- std::uint16_t height = frame_h / num_stripes +
- (i + 1 == num_stripes ? frame_h % num_stripes : 0);
- const auto* raw_start = &raw_screen[y * frame_w * frame_bpp];
- const auto* raw_end = raw_start + (height * frame_w * frame_bpp);
- // creating a named object and setting individual data members in order
- // to make klp happy
- // TODO (haining) construct this inside the call when not compiling
- // on klp
- Stripe s{};
- s.index = i;
- s.x = 0;
- s.y = y;
- s.width = frame_w;
- s.stride = frame_stride_bytes;
- s.height = height;
- s.frame_id = next_frame_number++;
- s.raw_data.assign(raw_start, raw_end);
- s.orientation = ScreenOrientation::Portrait;
- processed_frame.stripes_.push_back(std::move(s));
- }
-
- processed_frame.display_number_ = display_number;
- processed_frame.is_success_ = true;
- };
-}
-
-void SimulatedHWComposer::MakeStripes() {
- std::uint64_t stripe_seq_num = 1;
- /*
- * callback should be set before the first WaitForAtLeastOneClientConnection()
- * (b/178504150) and the first OnFrameAfter().
- */
- if (!screen_connector_.IsCallbackSet()) {
- LOG(FATAL) << "ScreenConnector callback hasn't been set before MakeStripes";
- }
- while (!closed()) {
- bb_->WaitForAtLeastOneClientConnection();
- auto sim_hw_processed_frame = screen_connector_.OnNextFrame();
- // sim_hw_processed_frame has display number from the guest
- if (!sim_hw_processed_frame.is_success_) {
- continue;
- }
- while (!sim_hw_processed_frame.stripes_.empty()) {
- /*
- * ScreenConnector that supplies the frames into the queue
- * cannot be aware of stripe_seq_num. The callback was set at the
- * ScreenConnector creation time. ScreenConnector calls the callback
- * function autonomously to make the processed frames to supply the
- * queue with.
- *
- * Besides, ScreenConnector is not VNC specific. Thus, stripe_seq_num,
- * a VNC specific information, is maintained here.
- *
- * OnFrameAfter returns a sim_hw_processed_frame, that contains N consecutive stripes.
- * each stripe s has an invalid seq_number, default-initialzed
- * We set the field properly, and push to the stripes_
- */
- auto& s = sim_hw_processed_frame.stripes_.front();
- stripe_seq_num++;
- s.seq_number = StripeSeqNumber{stripe_seq_num};
- stripes_.Push(std::move(s));
- sim_hw_processed_frame.stripes_.pop_front();
- }
- }
-}
-
-int SimulatedHWComposer::NumberOfStripes() { return kNumStripes; }
-
-void SimulatedHWComposer::ReportClientsConnected() {
- screen_connector_.ReportClientsConnected(true);
-}
diff --git a/host/frontend/vnc_server/simulated_hw_composer.h b/host/frontend/vnc_server/simulated_hw_composer.h
deleted file mode 100644
index 9d62e3e..0000000
--- a/host/frontend/vnc_server/simulated_hw_composer.h
+++ /dev/null
@@ -1,74 +0,0 @@
-#pragma once
-
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#include <condition_variable>
-#include <mutex>
-#ifdef FUZZ_TEST_VNC
-#include <random>
-#endif
-#include <thread>
-#include <deque>
-
-#include "common/libs/concurrency/thread_annotations.h"
-#include "common/libs/concurrency/thread_safe_queue.h"
-#include "host/frontend/vnc_server/blackboard.h"
-#include "host/frontend/vnc_server/vnc_utils.h"
-#include "host/libs/config/cuttlefish_config.h"
-#include "host/libs/screen_connector/screen_connector.h"
-
-namespace cuttlefish {
-namespace vnc {
-class SimulatedHWComposer {
- public:
- using GenerateProcessedFrameCallback = ScreenConnector::GenerateProcessedFrameCallback;
-
- SimulatedHWComposer(BlackBoard* bb, ScreenConnector& screen_connector);
- SimulatedHWComposer(const SimulatedHWComposer&) = delete;
- SimulatedHWComposer& operator=(const SimulatedHWComposer&) = delete;
- ~SimulatedHWComposer();
-
- Stripe GetNewStripe();
-
- void ReportClientsConnected();
-
- // NOTE not constexpr on purpose
- static int NumberOfStripes();
-
- private:
- bool closed();
- void close();
- static void EraseHalfOfElements(ThreadSafeQueue<Stripe>::QueueImpl* q);
- void MakeStripes();
- GenerateProcessedFrameCallback GetScreenConnectorCallback();
-
-#ifdef FUZZ_TEST_VNC
- std::default_random_engine engine_;
- std::uniform_int_distribution<int> random_ =
- std::uniform_int_distribution<int>{0, 2};
-#endif
- static constexpr int kNumStripes = 8;
- constexpr static std::size_t kMaxQueueElements = 64;
- bool closed_ GUARDED_BY(m_){};
- std::mutex m_;
- BlackBoard* bb_{};
- ThreadSafeQueue<Stripe> stripes_;
- std::thread stripe_maker_;
- ScreenConnector& screen_connector_;
-};
-} // namespace vnc
-} // namespace cuttlefish
diff --git a/host/frontend/vnc_server/virtual_inputs.cpp b/host/frontend/vnc_server/virtual_inputs.cpp
deleted file mode 100644
index 4876338..0000000
--- a/host/frontend/vnc_server/virtual_inputs.cpp
+++ /dev/null
@@ -1,412 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#include "host/frontend/vnc_server/virtual_inputs.h"
-
-#include <android-base/logging.h>
-#include <android-base/strings.h>
-#include <gflags/gflags.h>
-#include <linux/input.h>
-#include <linux/uinput.h>
-
-#include <cstdint>
-#include <mutex>
-#include <thread>
-#include "keysyms.h"
-
-#include "common/libs/confui/confui.h"
-#include "common/libs/fs/shared_select.h"
-#include "host/libs/config/cuttlefish_config.h"
-#include "host/libs/config/logging.h"
-
-using cuttlefish::vnc::VirtualInputs;
-
-DEFINE_string(touch_fds, "",
- "A list of fds for sockets where to accept touch connections");
-
-DEFINE_int32(keyboard_fd, -1,
- "A fd for a socket where to accept keyboard connections");
-
-DEFINE_bool(write_virtio_input, false,
- "Whether to write the virtio_input struct over the socket");
-
-namespace {
-// Necessary to define here as the virtio_input.h header is not available
-// in the host glibc.
-struct virtio_input_event {
- std::uint16_t type;
- std::uint16_t code;
- std::int32_t value;
-};
-
-void AddKeyMappings(std::map<uint32_t, uint16_t>* key_mapping) {
- (*key_mapping)[cuttlefish::xk::AltLeft] = KEY_LEFTALT;
- (*key_mapping)[cuttlefish::xk::ControlLeft] = KEY_LEFTCTRL;
- (*key_mapping)[cuttlefish::xk::ShiftLeft] = KEY_LEFTSHIFT;
- (*key_mapping)[cuttlefish::xk::AltRight] = KEY_RIGHTALT;
- (*key_mapping)[cuttlefish::xk::ControlRight] = KEY_RIGHTCTRL;
- (*key_mapping)[cuttlefish::xk::ShiftRight] = KEY_RIGHTSHIFT;
- (*key_mapping)[cuttlefish::xk::MetaLeft] = KEY_LEFTMETA;
- (*key_mapping)[cuttlefish::xk::MetaRight] = KEY_RIGHTMETA;
- (*key_mapping)[cuttlefish::xk::MultiKey] = KEY_COMPOSE;
-
- (*key_mapping)[cuttlefish::xk::CapsLock] = KEY_CAPSLOCK;
- (*key_mapping)[cuttlefish::xk::NumLock] = KEY_NUMLOCK;
- (*key_mapping)[cuttlefish::xk::ScrollLock] = KEY_SCROLLLOCK;
-
- (*key_mapping)[cuttlefish::xk::BackSpace] = KEY_BACKSPACE;
- (*key_mapping)[cuttlefish::xk::Tab] = KEY_TAB;
- (*key_mapping)[cuttlefish::xk::Return] = KEY_ENTER;
- (*key_mapping)[cuttlefish::xk::Escape] = KEY_ESC;
-
- (*key_mapping)[' '] = KEY_SPACE;
- (*key_mapping)['!'] = KEY_1;
- (*key_mapping)['"'] = KEY_APOSTROPHE;
- (*key_mapping)['#'] = KEY_3;
- (*key_mapping)['$'] = KEY_4;
- (*key_mapping)['%'] = KEY_5;
- (*key_mapping)['^'] = KEY_6;
- (*key_mapping)['&'] = KEY_7;
- (*key_mapping)['\''] = KEY_APOSTROPHE;
- (*key_mapping)['('] = KEY_9;
- (*key_mapping)[')'] = KEY_0;
- (*key_mapping)['*'] = KEY_8;
- (*key_mapping)['+'] = KEY_EQUAL;
- (*key_mapping)[','] = KEY_COMMA;
- (*key_mapping)['-'] = KEY_MINUS;
- (*key_mapping)['.'] = KEY_DOT;
- (*key_mapping)['/'] = KEY_SLASH;
- (*key_mapping)['0'] = KEY_0;
- (*key_mapping)['1'] = KEY_1;
- (*key_mapping)['2'] = KEY_2;
- (*key_mapping)['3'] = KEY_3;
- (*key_mapping)['4'] = KEY_4;
- (*key_mapping)['5'] = KEY_5;
- (*key_mapping)['6'] = KEY_6;
- (*key_mapping)['7'] = KEY_7;
- (*key_mapping)['8'] = KEY_8;
- (*key_mapping)['9'] = KEY_9;
- (*key_mapping)[':'] = KEY_SEMICOLON;
- (*key_mapping)[';'] = KEY_SEMICOLON;
- (*key_mapping)['<'] = KEY_COMMA;
- (*key_mapping)['='] = KEY_EQUAL;
- (*key_mapping)['>'] = KEY_DOT;
- (*key_mapping)['?'] = KEY_SLASH;
- (*key_mapping)['@'] = KEY_2;
- (*key_mapping)['A'] = KEY_A;
- (*key_mapping)['B'] = KEY_B;
- (*key_mapping)['C'] = KEY_C;
- (*key_mapping)['D'] = KEY_D;
- (*key_mapping)['E'] = KEY_E;
- (*key_mapping)['F'] = KEY_F;
- (*key_mapping)['G'] = KEY_G;
- (*key_mapping)['H'] = KEY_H;
- (*key_mapping)['I'] = KEY_I;
- (*key_mapping)['J'] = KEY_J;
- (*key_mapping)['K'] = KEY_K;
- (*key_mapping)['L'] = KEY_L;
- (*key_mapping)['M'] = KEY_M;
- (*key_mapping)['N'] = KEY_N;
- (*key_mapping)['O'] = KEY_O;
- (*key_mapping)['P'] = KEY_P;
- (*key_mapping)['Q'] = KEY_Q;
- (*key_mapping)['R'] = KEY_R;
- (*key_mapping)['S'] = KEY_S;
- (*key_mapping)['T'] = KEY_T;
- (*key_mapping)['U'] = KEY_U;
- (*key_mapping)['V'] = KEY_V;
- (*key_mapping)['W'] = KEY_W;
- (*key_mapping)['X'] = KEY_X;
- (*key_mapping)['Y'] = KEY_Y;
- (*key_mapping)['Z'] = KEY_Z;
- (*key_mapping)['['] = KEY_LEFTBRACE;
- (*key_mapping)['\\'] = KEY_BACKSLASH;
- (*key_mapping)[']'] = KEY_RIGHTBRACE;
- (*key_mapping)['-'] = KEY_MINUS;
- (*key_mapping)['_'] = KEY_MINUS;
- (*key_mapping)['`'] = KEY_GRAVE;
- (*key_mapping)['a'] = KEY_A;
- (*key_mapping)['b'] = KEY_B;
- (*key_mapping)['c'] = KEY_C;
- (*key_mapping)['d'] = KEY_D;
- (*key_mapping)['e'] = KEY_E;
- (*key_mapping)['f'] = KEY_F;
- (*key_mapping)['g'] = KEY_G;
- (*key_mapping)['h'] = KEY_H;
- (*key_mapping)['i'] = KEY_I;
- (*key_mapping)['j'] = KEY_J;
- (*key_mapping)['k'] = KEY_K;
- (*key_mapping)['l'] = KEY_L;
- (*key_mapping)['m'] = KEY_M;
- (*key_mapping)['n'] = KEY_N;
- (*key_mapping)['o'] = KEY_O;
- (*key_mapping)['p'] = KEY_P;
- (*key_mapping)['q'] = KEY_Q;
- (*key_mapping)['r'] = KEY_R;
- (*key_mapping)['s'] = KEY_S;
- (*key_mapping)['t'] = KEY_T;
- (*key_mapping)['u'] = KEY_U;
- (*key_mapping)['v'] = KEY_V;
- (*key_mapping)['w'] = KEY_W;
- (*key_mapping)['x'] = KEY_X;
- (*key_mapping)['y'] = KEY_Y;
- (*key_mapping)['z'] = KEY_Z;
- (*key_mapping)['{'] = KEY_LEFTBRACE;
- (*key_mapping)['\\'] = KEY_BACKSLASH;
- (*key_mapping)['|'] = KEY_BACKSLASH;
- (*key_mapping)['}'] = KEY_RIGHTBRACE;
- (*key_mapping)['~'] = KEY_GRAVE;
-
- (*key_mapping)[cuttlefish::xk::F1] = KEY_F1;
- (*key_mapping)[cuttlefish::xk::F2] = KEY_F2;
- (*key_mapping)[cuttlefish::xk::F3] = KEY_F3;
- (*key_mapping)[cuttlefish::xk::F4] = KEY_F4;
- (*key_mapping)[cuttlefish::xk::F5] = KEY_F5;
- (*key_mapping)[cuttlefish::xk::F6] = KEY_F6;
- (*key_mapping)[cuttlefish::xk::F7] = KEY_F7;
- (*key_mapping)[cuttlefish::xk::F8] = KEY_F8;
- (*key_mapping)[cuttlefish::xk::F9] = KEY_F9;
- (*key_mapping)[cuttlefish::xk::F10] = KEY_F10;
- (*key_mapping)[cuttlefish::xk::F11] = KEY_F11;
- (*key_mapping)[cuttlefish::xk::F12] = KEY_F12;
- (*key_mapping)[cuttlefish::xk::F13] = KEY_F13;
- (*key_mapping)[cuttlefish::xk::F14] = KEY_F14;
- (*key_mapping)[cuttlefish::xk::F15] = KEY_F15;
- (*key_mapping)[cuttlefish::xk::F16] = KEY_F16;
- (*key_mapping)[cuttlefish::xk::F17] = KEY_F17;
- (*key_mapping)[cuttlefish::xk::F18] = KEY_F18;
- (*key_mapping)[cuttlefish::xk::F19] = KEY_F19;
- (*key_mapping)[cuttlefish::xk::F20] = KEY_F20;
- (*key_mapping)[cuttlefish::xk::F21] = KEY_F21;
- (*key_mapping)[cuttlefish::xk::F22] = KEY_F22;
- (*key_mapping)[cuttlefish::xk::F23] = KEY_F23;
- (*key_mapping)[cuttlefish::xk::F24] = KEY_F24;
-
- (*key_mapping)[cuttlefish::xk::Keypad0] = KEY_KP0;
- (*key_mapping)[cuttlefish::xk::Keypad1] = KEY_KP1;
- (*key_mapping)[cuttlefish::xk::Keypad2] = KEY_KP2;
- (*key_mapping)[cuttlefish::xk::Keypad3] = KEY_KP3;
- (*key_mapping)[cuttlefish::xk::Keypad4] = KEY_KP4;
- (*key_mapping)[cuttlefish::xk::Keypad5] = KEY_KP5;
- (*key_mapping)[cuttlefish::xk::Keypad6] = KEY_KP6;
- (*key_mapping)[cuttlefish::xk::Keypad7] = KEY_KP7;
- (*key_mapping)[cuttlefish::xk::Keypad8] = KEY_KP8;
- (*key_mapping)[cuttlefish::xk::Keypad9] = KEY_KP9;
- (*key_mapping)[cuttlefish::xk::KeypadMultiply] = KEY_KPASTERISK;
- (*key_mapping)[cuttlefish::xk::KeypadSubtract] = KEY_KPMINUS;
- (*key_mapping)[cuttlefish::xk::KeypadAdd] = KEY_KPPLUS;
- (*key_mapping)[cuttlefish::xk::KeypadDecimal] = KEY_KPDOT;
- (*key_mapping)[cuttlefish::xk::KeypadEnter] = KEY_KPENTER;
- (*key_mapping)[cuttlefish::xk::KeypadDivide] = KEY_KPSLASH;
- (*key_mapping)[cuttlefish::xk::KeypadEqual] = KEY_KPEQUAL;
- (*key_mapping)[cuttlefish::xk::PlusMinus] = KEY_KPPLUSMINUS;
-
- (*key_mapping)[cuttlefish::xk::SysReq] = KEY_SYSRQ;
- (*key_mapping)[cuttlefish::xk::LineFeed] = KEY_LINEFEED;
- (*key_mapping)[cuttlefish::xk::Home] = KEY_HOME;
- (*key_mapping)[cuttlefish::xk::Up] = KEY_UP;
- (*key_mapping)[cuttlefish::xk::PageUp] = KEY_PAGEUP;
- (*key_mapping)[cuttlefish::xk::Left] = KEY_LEFT;
- (*key_mapping)[cuttlefish::xk::Right] = KEY_RIGHT;
- (*key_mapping)[cuttlefish::xk::End] = KEY_END;
- (*key_mapping)[cuttlefish::xk::Down] = KEY_DOWN;
- (*key_mapping)[cuttlefish::xk::PageDown] = KEY_PAGEDOWN;
- (*key_mapping)[cuttlefish::xk::Insert] = KEY_INSERT;
- (*key_mapping)[cuttlefish::xk::Delete] = KEY_DELETE;
- (*key_mapping)[cuttlefish::xk::Pause] = KEY_PAUSE;
- (*key_mapping)[cuttlefish::xk::KeypadSeparator] = KEY_KPCOMMA;
- (*key_mapping)[cuttlefish::xk::Yen] = KEY_YEN;
- (*key_mapping)[cuttlefish::xk::Cancel] = KEY_STOP;
- (*key_mapping)[cuttlefish::xk::Redo] = KEY_AGAIN;
- (*key_mapping)[cuttlefish::xk::Undo] = KEY_UNDO;
- (*key_mapping)[cuttlefish::xk::Find] = KEY_FIND;
- (*key_mapping)[cuttlefish::xk::Print] = KEY_PRINT;
- (*key_mapping)[cuttlefish::xk::VolumeDown] = KEY_VOLUMEDOWN;
- (*key_mapping)[cuttlefish::xk::Mute] = KEY_MUTE;
- (*key_mapping)[cuttlefish::xk::VolumeUp] = KEY_VOLUMEUP;
- (*key_mapping)[cuttlefish::xk::Menu] = KEY_MENU;
- (*key_mapping)[cuttlefish::xk::VNCMenu] = KEY_MENU;
-}
-
-void InitInputEvent(struct input_event* evt, uint16_t type, uint16_t code,
- int32_t value) {
- evt->type = type;
- evt->code = code;
- evt->value = value;
-}
-
-} // namespace
-
-class SocketVirtualInputs : public VirtualInputs {
- public:
- SocketVirtualInputs()
- : client_connector_([this]() { ClientConnectorLoop(); }) {}
-
- void GenerateKeyPressEvent(int key_code, bool down) override {
- struct input_event events[2];
- InitInputEvent(&events[0], EV_KEY, keymapping_[key_code], down);
- InitInputEvent(&events[1], EV_SYN, 0, 0);
-
- SendEvents(keyboard_socket_, events);
- }
-
- void PressPowerButton(bool down) override {
- struct input_event events[2];
- InitInputEvent(&events[0], EV_KEY, KEY_POWER, down);
- InitInputEvent(&events[1], EV_SYN, 0, 0);
-
- SendEvents(keyboard_socket_, events);
- }
-
- void HandlePointerEvent(bool touch_down, int x, int y) override {
- // TODO(b/124121375): Use multitouch when available
- struct input_event events[4];
- InitInputEvent(&events[0], EV_ABS, ABS_X, x);
- InitInputEvent(&events[1], EV_ABS, ABS_Y, y);
- InitInputEvent(&events[2], EV_KEY, BTN_TOUCH, touch_down);
- InitInputEvent(&events[3], EV_SYN, 0, 0);
-
- SendEvents(touch_socket_, events);
- }
-
- private:
- template<size_t num_events>
- void SendEvents(cuttlefish::SharedFD socket, struct input_event (&event_buffer)[num_events]) {
- std::lock_guard<std::mutex> lock(socket_mutex_);
- if (!socket->IsOpen()) {
- // This is unlikely as it would only happen between the start of the vnc
- // server and the connection of the VMM to the socket.
- // If it happens, just drop the events as the VM is not yet ready to
- // handle it.
- return;
- }
-
- if (FLAGS_write_virtio_input) {
- struct virtio_input_event virtio_events[num_events];
- for (size_t i = 0; i < num_events; i++) {
- virtio_events[i] = (struct virtio_input_event) {
- .type = event_buffer[i].type,
- .code = event_buffer[i].code,
- .value = event_buffer[i].value,
- };
- }
- auto ret = socket->Write(virtio_events, sizeof(virtio_events));
- if (ret < 0) {
- LOG(ERROR) << "Error sending input events: " << socket->StrError();
- }
- } else {
- auto ret = socket->Write(event_buffer, sizeof(event_buffer));
- if (ret < 0) {
- LOG(ERROR) << "Error sending input events: " << socket->StrError();
- }
- }
- }
-
- void ClientConnectorLoop() {
- auto touch_fd =
- std::stoi(android::base::Split(FLAGS_touch_fds, ",").front());
- auto touch_server = cuttlefish::SharedFD::Dup(touch_fd);
- close(touch_fd);
-
- auto keyboard_server = cuttlefish::SharedFD::Dup(FLAGS_keyboard_fd);
- close(FLAGS_keyboard_fd);
- FLAGS_keyboard_fd = -1;
- LOG(DEBUG) << "Input socket host accepting connections...";
-
- while (1) {
- cuttlefish::SharedFDSet read_set;
- read_set.Set(touch_server);
- read_set.Set(keyboard_server);
- cuttlefish::Select(&read_set, nullptr, nullptr, nullptr);
- {
- std::lock_guard<std::mutex> lock(socket_mutex_);
- if (read_set.IsSet(touch_server)) {
- touch_socket_ = cuttlefish::SharedFD::Accept(*touch_server);
- LOG(DEBUG) << "connected to touch";
- }
- if (read_set.IsSet(keyboard_server)) {
- keyboard_socket_ = cuttlefish::SharedFD::Accept(*keyboard_server);
- LOG(DEBUG) << "connected to keyboard";
- }
- }
- }
- }
- cuttlefish::SharedFD touch_socket_;
- cuttlefish::SharedFD keyboard_socket_;
- std::thread client_connector_;
- std::mutex socket_mutex_;
-};
-
-VirtualInputs::VirtualInputs() { AddKeyMappings(&keymapping_); }
-
-/**
- * Depending on the host mode (e.g. android, confirmation ui(tee), etc)
- * deliver the inputs to the right input implementation
- * e.g. ConfUI's input or regular socket based input
- */
-class VirtualInputDemux : public VirtualInputs {
- public:
- VirtualInputDemux(cuttlefish::confui::HostVirtualInput& confui_input)
- : confui_input_{confui_input} {}
- virtual ~VirtualInputDemux() = default;
-
- virtual void GenerateKeyPressEvent(int code, bool down) override;
- virtual void PressPowerButton(bool down) override;
- virtual void HandlePointerEvent(bool touch_down, int x, int y) override;
-
- private:
- SocketVirtualInputs socket_virtual_input_;
- cuttlefish::confui::HostVirtualInput& confui_input_;
-};
-
-void VirtualInputDemux::GenerateKeyPressEvent(int code, bool down) {
- // confui input is active only in the confirmation UI
- // also, socket virtual input should be inactive in the confirmation
- // UI session
- if (confui_input_.IsConfUiActive()) {
- if (code == cuttlefish::xk::Menu) {
- // release menu button in confirmation UI means for now cancel
- confui_input_.PressCancelButton(down);
- }
- ConfUiLog(DEBUG) << "the key" << code << "ignored."
- << "currently confirmation UI handles"
- << "menu and power only.";
- return;
- }
- socket_virtual_input_.GenerateKeyPressEvent(code, down);
-}
-
-void VirtualInputDemux::PressPowerButton(bool down) {
- if (confui_input_.IsConfUiActive()) {
- confui_input_.PressConfirmButton(down);
- return;
- }
- socket_virtual_input_.PressPowerButton(down);
-}
-
-void VirtualInputDemux::HandlePointerEvent(bool touch_down, int x, int y) {
- if (confui_input_.IsConfUiActive()) {
- ConfUiLog(DEBUG) << "currently confirmation UI ignores pointer events at ("
- << x << ", " << y << ")";
- return;
- }
- socket_virtual_input_.HandlePointerEvent(touch_down, x, y);
-}
-
-std::shared_ptr<VirtualInputs> VirtualInputs::Get(
- cuttlefish::confui::HostVirtualInput& confui_input) {
- return std::make_shared<VirtualInputDemux>(confui_input);
-}
diff --git a/host/frontend/vnc_server/virtual_inputs.h b/host/frontend/vnc_server/virtual_inputs.h
deleted file mode 100644
index f30202e..0000000
--- a/host/frontend/vnc_server/virtual_inputs.h
+++ /dev/null
@@ -1,47 +0,0 @@
-#pragma once
-
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#include <map>
-#include <memory>
-#include <mutex>
-
-#include "host/libs/confui/host_virtual_input.h"
-#include "vnc_utils.h"
-
-namespace cuttlefish {
-namespace vnc {
-
-class VirtualInputs {
- public:
- static std::shared_ptr<VirtualInputs> Get(
- cuttlefish::confui::HostVirtualInput& confui_input);
-
- virtual ~VirtualInputs() = default;
-
- virtual void GenerateKeyPressEvent(int code, bool down) = 0;
- virtual void PressPowerButton(bool down) = 0;
- virtual void HandlePointerEvent(bool touch_down, int x, int y) = 0;
-
- protected:
- VirtualInputs();
-
- std::map<uint32_t, uint16_t> keymapping_;
-};
-
-} // namespace vnc
-} // namespace cuttlefish
diff --git a/host/frontend/vnc_server/vnc_client_connection.cpp b/host/frontend/vnc_server/vnc_client_connection.cpp
deleted file mode 100644
index ce16ec8..0000000
--- a/host/frontend/vnc_server/vnc_client_connection.cpp
+++ /dev/null
@@ -1,685 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#include "host/frontend/vnc_server/vnc_client_connection.h"
-
-#include <netinet/in.h>
-#include <sys/time.h>
-
-#include <algorithm>
-#include <cmath>
-#include <cstdint>
-#include <cstring>
-#include <memory>
-#include <mutex>
-#include <string>
-#include <thread>
-#include <utility>
-#include <vector>
-
-#include <gflags/gflags.h>
-#include <android-base/logging.h>
-#include "common/libs/utils/tcp_socket.h"
-#include "host/frontend/vnc_server/keysyms.h"
-#include "host/frontend/vnc_server/mocks.h"
-#include "host/frontend/vnc_server/vnc_utils.h"
-#include "host/libs/config/cuttlefish_config.h"
-#include "host/libs/screen_connector/screen_connector.h"
-
-using cuttlefish::Message;
-using cuttlefish::vnc::Stripe;
-using cuttlefish::vnc::StripePtrVec;
-using cuttlefish::vnc::VncClientConnection;
-
-struct ScreenRegionView {
- using Pixel = uint32_t;
- static constexpr int kSwiftShaderPadding = 4;
- static constexpr int kRedShift = 0;
- static constexpr int kGreenShift = 8;
- static constexpr int kBlueShift = 16;
- static constexpr int kRedBits = 8;
- static constexpr int kGreenBits = 8;
- static constexpr int kBlueBits = 8;
-};
-
-DEFINE_bool(debug_client, false, "Turn on detailed logging for the client");
-
-#define DLOG(LEVEL) \
- if (FLAGS_debug_client) LOG(LEVEL)
-
-namespace {
-class BigEndianChecker {
- public:
- BigEndianChecker() {
- uint32_t u = 1;
- is_big_endian_ = *reinterpret_cast<const char*>(&u) == 0;
- }
- bool operator()() const { return is_big_endian_; }
-
- private:
- bool is_big_endian_{};
-};
-
-const BigEndianChecker ImBigEndian;
-
-constexpr int32_t kDesktopSizeEncoding = -223;
-constexpr int32_t kTightEncoding = 7;
-
-// These are the lengths not counting the first byte. The first byte
-// indicates the message type.
-constexpr size_t kSetPixelFormatLength = 19;
-constexpr size_t kFramebufferUpdateRequestLength = 9;
-constexpr size_t kSetEncodingsLength = 3; // more bytes follow
-constexpr size_t kKeyEventLength = 7;
-constexpr size_t kPointerEventLength = 5;
-constexpr size_t kClientCutTextLength = 7; // more bytes follow
-
-std::string HostName() {
- auto config = cuttlefish::CuttlefishConfig::Get();
- auto instance = config->ForDefaultInstance();
- return !config || instance.device_title().empty() ? std::string{"localhost"}
- : instance.device_title();
-}
-
-std::uint16_t uint16_tAt(const void* p) {
- std::uint16_t u{};
- std::memcpy(&u, p, sizeof u);
- return ntohs(u);
-}
-
-std::uint32_t uint32_tAt(const void* p) {
- std::uint32_t u{};
- std::memcpy(&u, p, sizeof u);
- return ntohl(u);
-}
-
-std::int32_t int32_tAt(const void* p) {
- std::uint32_t u{};
- std::memcpy(&u, p, sizeof u);
- u = ntohl(u);
- std::int32_t s{};
- std::memcpy(&s, &u, sizeof s);
- return s;
-}
-
-std::uint32_t RedVal(std::uint32_t pixel) {
- return (pixel >> ScreenRegionView::kRedShift) &
- ((0x1 << ScreenRegionView::kRedBits) - 1);
-}
-
-std::uint32_t BlueVal(std::uint32_t pixel) {
- return (pixel >> ScreenRegionView::kBlueShift) &
- ((0x1 << ScreenRegionView::kBlueBits) - 1);
-}
-
-std::uint32_t GreenVal(std::uint32_t pixel) {
- return (pixel >> ScreenRegionView::kGreenShift) &
- ((0x1 << ScreenRegionView::kGreenBits) - 1);
-}
-} // namespace
-namespace cuttlefish {
-namespace vnc {
-bool operator==(const VncClientConnection::FrameBufferUpdateRequest& lhs,
- const VncClientConnection::FrameBufferUpdateRequest& rhs) {
- return lhs.x_pos == rhs.x_pos && lhs.y_pos == rhs.y_pos &&
- lhs.width == rhs.width && lhs.height == rhs.height;
-}
-
-bool operator!=(const VncClientConnection::FrameBufferUpdateRequest& lhs,
- const VncClientConnection::FrameBufferUpdateRequest& rhs) {
- return !(lhs == rhs);
-}
-} // namespace vnc
-} // namespace cuttlefish
-
-VncClientConnection::VncClientConnection(
- ClientSocket client, std::shared_ptr<VirtualInputs> virtual_inputs,
- BlackBoard* bb, bool aggressive)
- : client_{std::move(client)}, virtual_inputs_{virtual_inputs}, bb_{bb} {
- frame_buffer_request_handler_tid_ = std::thread(
- &VncClientConnection::FrameBufferUpdateRequestHandler, this, aggressive);
-}
-
-VncClientConnection::~VncClientConnection() {
- {
- std::lock_guard<std::mutex> guard(m_);
- closed_ = true;
- }
- bb_->StopWaiting(this);
- frame_buffer_request_handler_tid_.join();
-}
-
-void VncClientConnection::StartSession() {
- LOG(INFO) << "Starting session";
- SetupProtocol();
- LOG(INFO) << "Protocol set up";
- if (client_.closed()) {
- return;
- }
- SetupSecurityType();
- LOG(INFO) << "Security type set";
- if (client_.closed()) {
- return;
- }
- GetClientInit();
- LOG(INFO) << "Gotten client init";
- if (client_.closed()) {
- return;
- }
- SendServerInit();
- LOG(INFO) << "Sent server init";
- if (client_.closed()) {
- return;
- }
- NormalSession();
- LOG(INFO) << "vnc session terminated";
-}
-
-bool VncClientConnection::closed() {
- std::lock_guard<std::mutex> guard(m_);
- return closed_;
-}
-
-void VncClientConnection::SetupProtocol() {
- static constexpr char kRFBVersion[] = "RFB 003.008\n";
- static constexpr char kRFBVersionOld[] = "RFB 003.003\n";
- static constexpr auto kVersionLen = (sizeof kRFBVersion) - 1;
- client_.SendNoSignal(reinterpret_cast<const std::uint8_t*>(kRFBVersion),
- kVersionLen);
- auto client_protocol = client_.Recv(kVersionLen);
- if (std::memcmp(&client_protocol[0], kRFBVersion,
- std::min(kVersionLen, client_protocol.size())) != 0) {
- if (!std::memcmp(
- &client_protocol[0],
- kRFBVersionOld,
- std::min(kVersionLen, client_protocol.size()))) {
- // We'll deal with V3.3 as well.
- client_is_old_ = true;
- return;
- }
-
- client_protocol.push_back('\0');
- LOG(ERROR) << "vnc client wants a different protocol: "
- << reinterpret_cast<const char*>(&client_protocol[0]);
- }
-}
-
-void VncClientConnection::SetupSecurityType() {
- if (client_is_old_) {
- static constexpr std::uint8_t kVNCSecurity[4] = { 0x00, 0x00, 0x00, 0x02 };
- client_.SendNoSignal(kVNCSecurity);
-
- static constexpr std::uint8_t kChallenge[16] =
- { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 };
-
- client_.SendNoSignal(kChallenge);
-
- auto clientResponse = client_.Recv(16);
- (void)clientResponse; // Accept any response, we're not interested in actual security.
-
- static constexpr std::uint8_t kSuccess[4] = { 0x00, 0x00, 0x00, 0x00 };
- client_.SendNoSignal(kSuccess);
- return;
- }
-
- static constexpr std::uint8_t kNoneSecurity = 0x1;
- // The first '0x1' indicates the number of items that follow
- static constexpr std::uint8_t kOnlyNoneSecurity[] = {0x01, kNoneSecurity};
- client_.SendNoSignal(kOnlyNoneSecurity);
- auto client_security = client_.Recv(1);
- if (client_.closed()) {
- return;
- }
- if (client_security.front() != kNoneSecurity) {
- LOG(ERROR) << "vnc client is asking for security type "
- << static_cast<int>(client_security.front());
- }
- static constexpr std::uint8_t kZero[4] = {};
- client_.SendNoSignal(kZero);
-}
-
-void VncClientConnection::GetClientInit() {
- auto client_shared = client_.Recv(1);
-}
-
-void VncClientConnection::SendServerInit() {
- const std::string server_name = HostName();
- std::lock_guard<std::mutex> guard(m_);
- auto server_init = cuttlefish::CreateMessage(
- static_cast<std::uint16_t>(ScreenWidth()),
- static_cast<std::uint16_t>(ScreenHeight()), pixel_format_.bits_per_pixel,
- pixel_format_.depth, pixel_format_.big_endian, pixel_format_.true_color,
- pixel_format_.red_max, pixel_format_.green_max, pixel_format_.blue_max,
- pixel_format_.red_shift, pixel_format_.green_shift,
- pixel_format_.blue_shift, std::uint16_t{}, // padding
- std::uint8_t{}, // padding
- static_cast<std::uint32_t>(server_name.size()), server_name);
- client_.SendNoSignal(server_init);
-}
-
-Message VncClientConnection::MakeFrameBufferUpdateHeader(
- std::uint16_t num_stripes) {
- return cuttlefish::CreateMessage(std::uint8_t{0}, // message-type
- std::uint8_t{}, // padding
- std::uint16_t{num_stripes});
-}
-
-void VncClientConnection::AppendRawStripeHeader(Message* frame_buffer_update,
- const Stripe& stripe) {
- static constexpr int32_t kRawEncoding = 0;
- cuttlefish::AppendToMessage(frame_buffer_update, std::uint16_t{stripe.x},
- std::uint16_t{stripe.y}, std::uint16_t{stripe.width},
- std::uint16_t{stripe.height}, kRawEncoding);
-}
-
-void VncClientConnection::AppendJpegSize(Message* frame_buffer_update,
- size_t jpeg_size) {
- constexpr size_t kJpegSizeOneByteMax = 127;
- constexpr size_t kJpegSizeTwoByteMax = 16383;
- constexpr size_t kJpegSizeThreeByteMax = 4194303;
-
- if (jpeg_size <= kJpegSizeOneByteMax) {
- cuttlefish::AppendToMessage(frame_buffer_update,
- static_cast<std::uint8_t>(jpeg_size));
- } else if (jpeg_size <= kJpegSizeTwoByteMax) {
- auto sz = static_cast<std::uint32_t>(jpeg_size);
- cuttlefish::AppendToMessage(frame_buffer_update,
- static_cast<std::uint8_t>((sz & 0x7F) | 0x80),
- static_cast<std::uint8_t>((sz >> 7) & 0xFF));
- } else {
- if (jpeg_size > kJpegSizeThreeByteMax) {
- LOG(FATAL) << "jpeg size is too big: " << jpeg_size << " must be under "
- << kJpegSizeThreeByteMax;
- }
- const auto sz = static_cast<std::uint32_t>(jpeg_size);
- cuttlefish::AppendToMessage(frame_buffer_update,
- static_cast<std::uint8_t>((sz & 0x7F) | 0x80),
- static_cast<std::uint8_t>(((sz >> 7) & 0x7F) | 0x80),
- static_cast<std::uint8_t>((sz >> 14) & 0xFF));
- }
-}
-
-void VncClientConnection::AppendRawStripe(Message* frame_buffer_update,
- const Stripe& stripe) const {
- using Pixel = ScreenRegionView::Pixel;
- auto& fbu = *frame_buffer_update;
- AppendRawStripeHeader(&fbu, stripe);
- auto init_size = fbu.size();
- fbu.insert(fbu.end(), stripe.raw_data.begin(), stripe.raw_data.end());
- for (size_t i = init_size; i < fbu.size(); i += sizeof(Pixel)) {
- CHECK_LE(i + sizeof(Pixel), fbu.size());
- Pixel raw_pixel{};
- std::memcpy(&raw_pixel, &fbu[i], sizeof raw_pixel);
- auto red = RedVal(raw_pixel);
- auto green = GreenVal(raw_pixel);
- auto blue = BlueVal(raw_pixel);
- Pixel pixel = Pixel{red} << pixel_format_.red_shift |
- Pixel{blue} << pixel_format_.blue_shift |
- Pixel{green} << pixel_format_.green_shift;
-
- if (bool(pixel_format_.big_endian) != ImBigEndian()) {
- // flip them bits (refactor into function)
- auto p = reinterpret_cast<char*>(&pixel);
- std::swap(p[0], p[3]);
- std::swap(p[1], p[2]);
- }
- std::memcpy(&fbu[i], &pixel, sizeof pixel);
- }
-}
-
-Message VncClientConnection::MakeRawFrameBufferUpdate(
- const StripePtrVec& stripes) const {
- auto fbu =
- MakeFrameBufferUpdateHeader(static_cast<std::uint16_t>(stripes.size()));
- for (auto& stripe : stripes) {
- AppendRawStripe(&fbu, *stripe);
- }
- return fbu;
-}
-
-void VncClientConnection::AppendJpegStripeHeader(Message* frame_buffer_update,
- const Stripe& stripe) {
- static constexpr std::uint8_t kJpegEncoding = 0x90;
- cuttlefish::AppendToMessage(frame_buffer_update, stripe.x, stripe.y, stripe.width,
- stripe.height, kTightEncoding, kJpegEncoding);
- AppendJpegSize(frame_buffer_update, stripe.jpeg_data.size());
-}
-
-void VncClientConnection::AppendJpegStripe(Message* frame_buffer_update,
- const Stripe& stripe) {
- AppendJpegStripeHeader(frame_buffer_update, stripe);
- frame_buffer_update->insert(frame_buffer_update->end(),
- stripe.jpeg_data.begin(), stripe.jpeg_data.end());
-}
-
-Message VncClientConnection::MakeJpegFrameBufferUpdate(
- const StripePtrVec& stripes) {
- auto fbu =
- MakeFrameBufferUpdateHeader(static_cast<std::uint16_t>(stripes.size()));
- for (auto& stripe : stripes) {
- AppendJpegStripe(&fbu, *stripe);
- }
- return fbu;
-}
-
-Message VncClientConnection::MakeFrameBufferUpdate(
- const StripePtrVec& stripes) {
- return use_jpeg_compression_ ? MakeJpegFrameBufferUpdate(stripes)
- : MakeRawFrameBufferUpdate(stripes);
-}
-
-void VncClientConnection::FrameBufferUpdateRequestHandler(bool aggressive) {
- BlackBoard::Registerer reg(bb_, this);
-
- while (!closed()) {
- auto stripes = bb_->WaitForSenderWork(this);
- if (closed()) {
- break;
- }
- if (stripes.empty()) {
- LOG(FATAL) << "Got 0 stripes";
- }
- {
- // lock here so a portrait frame can't be sent after a landscape
- // DesktopSize update, or vice versa.
- std::lock_guard<std::mutex> guard(m_);
- DLOG(INFO) << "Sending update in "
- << (current_orientation_ == ScreenOrientation::Portrait
- ? "portrait"
- : "landscape")
- << " mode";
- client_.SendNoSignal(MakeFrameBufferUpdate(stripes));
- }
- if (aggressive) {
- bb_->FrameBufferUpdateRequestReceived(this);
- }
- }
-}
-
-void VncClientConnection::SendDesktopSizeUpdate() {
- static constexpr int32_t kDesktopSizeEncoding = -223;
- client_.SendNoSignal(cuttlefish::CreateMessage(
- std::uint8_t{0}, // message-type,
- std::uint8_t{}, // padding
- std::uint16_t{1}, // one pseudo rectangle
- std::uint16_t{0}, std::uint16_t{0},
- static_cast<std::uint16_t>(ScreenWidth()),
- static_cast<std::uint16_t>(ScreenHeight()), kDesktopSizeEncoding));
-}
-
-bool VncClientConnection::IsUrgent(
- const FrameBufferUpdateRequest& update_request) const {
- return !update_request.incremental ||
- update_request != previous_update_request_;
-}
-
-void VncClientConnection::HandleFramebufferUpdateRequest() {
- auto msg = client_.Recv(kFramebufferUpdateRequestLength);
- if (msg.size() != kFramebufferUpdateRequestLength) {
- return;
- }
- FrameBufferUpdateRequest fbur{msg[1] == 0, uint16_tAt(&msg[1]),
- uint16_tAt(&msg[3]), uint16_tAt(&msg[5]),
- uint16_tAt(&msg[7])};
- if (IsUrgent(fbur)) {
- bb_->SignalClientNeedsEntireScreen(this);
- }
- bb_->FrameBufferUpdateRequestReceived(this);
- previous_update_request_ = fbur;
-}
-
-void VncClientConnection::HandleSetEncodings() {
- auto msg = client_.Recv(kSetEncodingsLength);
- if (msg.size() != kSetEncodingsLength) {
- return;
- }
- auto count = uint16_tAt(&msg[1]);
- auto encodings = client_.Recv(count * sizeof(int32_t));
- if (encodings.size() % sizeof(int32_t) != 0) {
- return;
- }
- {
- std::lock_guard<std::mutex> guard(m_);
- use_jpeg_compression_ = false;
- }
- for (size_t i = 0; i < encodings.size(); i += sizeof(int32_t)) {
- auto enc = int32_tAt(&encodings[i]);
- DLOG(INFO) << "client requesting encoding: " << enc;
- if (enc == kTightEncoding) {
- // This is a deviation from the spec which says that if a jpeg quality
- // level is not specified, tight encoding won't use jpeg.
- std::lock_guard<std::mutex> guard(m_);
- use_jpeg_compression_ = true;
- }
- if (kJpegMinQualityEncoding <= enc && enc <= kJpegMaxQualityEncoding) {
- DLOG(INFO) << "jpeg compression level: " << enc;
- bb_->set_jpeg_quality_level(enc);
- }
- if (enc == kDesktopSizeEncoding) {
- supports_desktop_size_encoding_ = true;
- }
- }
-}
-
-void VncClientConnection::HandleSetPixelFormat() {
- std::lock_guard<std::mutex> guard(m_);
- auto msg = client_.Recv(kSetPixelFormatLength);
- if (msg.size() != kSetPixelFormatLength) {
- return;
- }
- pixel_format_.bits_per_pixel = msg[3];
- pixel_format_.depth = msg[4];
- pixel_format_.big_endian = msg[5];
- pixel_format_.true_color = msg[7];
- pixel_format_.red_max = uint16_tAt(&msg[8]);
- pixel_format_.green_max = uint16_tAt(&msg[10]);
- pixel_format_.blue_max = uint16_tAt(&msg[12]);
- pixel_format_.red_shift = msg[13];
- pixel_format_.green_shift = msg[14];
- pixel_format_.blue_shift = msg[15];
-}
-
-void VncClientConnection::HandlePointerEvent() {
- auto msg = client_.Recv(kPointerEventLength);
- if (msg.size() != kPointerEventLength) {
- return;
- }
- std::uint8_t button_mask = msg[0];
- auto x_pos = uint16_tAt(&msg[1]);
- auto y_pos = uint16_tAt(&msg[3]);
- {
- std::lock_guard<std::mutex> guard(m_);
- if (current_orientation_ == ScreenOrientation::Landscape) {
- std::tie(x_pos, y_pos) =
- std::make_pair(ScreenConnectorInfo::ScreenWidth(0) - y_pos, x_pos);
- }
- }
- virtual_inputs_->HandlePointerEvent(button_mask, x_pos, y_pos);
-}
-
-void VncClientConnection::UpdateAccelerometer(float /*x*/, float /*y*/,
- float /*z*/) {
- // TODO(jemoreira): Implement when vsoc sensor hal is updated
-}
-
-VncClientConnection::Coordinates VncClientConnection::CoordinatesForOrientation(
- ScreenOrientation orientation) const {
- // Compute the acceleration vector that we need to send to mimic
- // this change.
- constexpr float g = 9.81;
- constexpr float angle = 20.0;
- const float cos_angle = std::cos(angle / M_PI);
- const float sin_angle = std::sin(angle / M_PI);
- const float z = g * sin_angle;
- switch (orientation) {
- case ScreenOrientation::Portrait:
- return {0, g * cos_angle, z};
- case ScreenOrientation::Landscape:
- return {g * cos_angle, 0, z};
- }
-}
-
-int VncClientConnection::ScreenWidth() const {
- return current_orientation_ == ScreenOrientation::Portrait
- ? ScreenConnectorInfo::ScreenWidth(0)
- : ScreenConnectorInfo::ScreenHeight(0);
-}
-
-int VncClientConnection::ScreenHeight() const {
- return current_orientation_ == ScreenOrientation::Portrait
- ? ScreenConnectorInfo::ScreenHeight(0)
- : ScreenConnectorInfo::ScreenWidth(0);
-}
-
-void VncClientConnection::SetScreenOrientation(ScreenOrientation orientation) {
- std::lock_guard<std::mutex> guard(m_);
- auto coords = CoordinatesForOrientation(orientation);
- UpdateAccelerometer(coords.x, coords.y, coords.z);
- if (supports_desktop_size_encoding_) {
- auto previous_orientation = current_orientation_;
- current_orientation_ = orientation;
- if (current_orientation_ != previous_orientation &&
- supports_desktop_size_encoding_) {
- SendDesktopSizeUpdate();
- bb_->SetOrientation(this, current_orientation_);
- // TODO not sure if I should be sending a frame update along with this,
- // or just letting the next FBUR handle it. This seems to me like it's
- // sending one more frame buffer update than was requested, which is
- // maybe a violation of the spec?
- }
- }
-}
-
-bool VncClientConnection::RotateIfIsRotationCommand(std::uint32_t key) {
- // Due to different configurations on different platforms we're supporting
- // a set of options for rotating the screen. These are similar to what
- // the emulator supports and has supported.
- // ctrl+left and ctrl+right work on windows and linux
- // command+left and command+right work on Mac
- // ctrl+fn+F11 and ctrl+fn+F12 work when chromoting to ubuntu from a Mac
- if (!control_key_down_ && !meta_key_down_) {
- return false;
- }
- switch (key) {
- case cuttlefish::xk::Right:
- case cuttlefish::xk::F12:
- DLOG(INFO) << "switching to portrait";
- SetScreenOrientation(ScreenOrientation::Portrait);
- break;
- case cuttlefish::xk::Left:
- case cuttlefish::xk::F11:
- DLOG(INFO) << "switching to landscape";
- SetScreenOrientation(ScreenOrientation::Landscape);
- break;
- default:
- return false;
- }
- return true;
-}
-
-void VncClientConnection::HandleKeyEvent() {
- auto msg = client_.Recv(kKeyEventLength);
- if (msg.size() != kKeyEventLength) {
- return;
- }
-
- auto key = uint32_tAt(&msg[3]);
- bool key_down = msg[0];
- switch (key) {
- case cuttlefish::xk::ControlLeft:
- case cuttlefish::xk::ControlRight:
- control_key_down_ = key_down;
- break;
- case cuttlefish::xk::MetaLeft:
- case cuttlefish::xk::MetaRight:
- meta_key_down_ = key_down;
- break;
- case cuttlefish::xk::F5:
- key = cuttlefish::xk::Menu;
- break;
- case cuttlefish::xk::F7:
- virtual_inputs_->PressPowerButton(key_down);
- return;
- default:
- break;
- }
-
- if (RotateIfIsRotationCommand(key)) {
- return;
- }
-
- virtual_inputs_->GenerateKeyPressEvent(key, key_down);
-}
-
-void VncClientConnection::HandleClientCutText() {
- auto msg = client_.Recv(kClientCutTextLength);
- if (msg.size() != kClientCutTextLength) {
- return;
- }
- auto len = uint32_tAt(&msg[3]);
- client_.Recv(len);
-}
-
-void VncClientConnection::NormalSession() {
- static constexpr std::uint8_t kSetPixelFormatMessage{0};
- static constexpr std::uint8_t kSetEncodingsMessage{2};
- static constexpr std::uint8_t kFramebufferUpdateRequestMessage{3};
- static constexpr std::uint8_t kKeyEventMessage{4};
- static constexpr std::uint8_t kPointerEventMessage{5};
- static constexpr std::uint8_t kClientCutTextMessage{6};
- while (true) {
- if (client_.closed()) {
- return;
- }
- auto msg = client_.Recv(1);
- if (client_.closed()) {
- return;
- }
- auto msg_type = msg.front();
- DLOG(INFO) << "Received message type " << msg_type;
-
- switch (msg_type) {
- case kSetPixelFormatMessage:
- HandleSetPixelFormat();
- break;
-
- case kSetEncodingsMessage:
- HandleSetEncodings();
- break;
-
- case kFramebufferUpdateRequestMessage:
- HandleFramebufferUpdateRequest();
- break;
-
- case kKeyEventMessage:
- HandleKeyEvent();
- break;
-
- case kPointerEventMessage:
- HandlePointerEvent();
- break;
-
- case kClientCutTextMessage:
- HandleClientCutText();
- break;
-
- default:
- LOG(WARNING) << "message type not handled: "
- << static_cast<int>(msg_type);
- break;
- }
- }
-}
diff --git a/host/frontend/vnc_server/vnc_client_connection.h b/host/frontend/vnc_server/vnc_client_connection.h
deleted file mode 100644
index b26cf86..0000000
--- a/host/frontend/vnc_server/vnc_client_connection.h
+++ /dev/null
@@ -1,173 +0,0 @@
-#pragma once
-
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#include "common/libs/concurrency/thread_annotations.h"
-#include "common/libs/fs/shared_fd.h"
-
-#include <cstdint>
-#include <memory>
-#include <mutex>
-#include <thread>
-#include <vector>
-
-#include "common/libs/utils/tcp_socket.h"
-#include "host/frontend/vnc_server/blackboard.h"
-#include "host/frontend/vnc_server/virtual_inputs.h"
-#include "host/frontend/vnc_server/vnc_utils.h"
-
-namespace cuttlefish {
-namespace vnc {
-
-class VncClientConnection {
- public:
- VncClientConnection(ClientSocket client,
- std::shared_ptr<VirtualInputs> virtual_inputs,
- BlackBoard* bb, bool aggressive);
- VncClientConnection(const VncClientConnection&) = delete;
- VncClientConnection& operator=(const VncClientConnection&) = delete;
- ~VncClientConnection();
-
- void StartSession();
-
- private:
- struct PixelFormat {
- std::uint8_t bits_per_pixel;
- std::uint8_t depth;
- std::uint8_t big_endian;
- std::uint8_t true_color;
- std::uint16_t red_max;
- std::uint16_t green_max;
- std::uint16_t blue_max;
- std::uint8_t red_shift;
- std::uint8_t green_shift;
- std::uint8_t blue_shift;
- };
-
- struct FrameBufferUpdateRequest {
- bool incremental;
- std::uint16_t x_pos;
- std::uint16_t y_pos;
- std::uint16_t width;
- std::uint16_t height;
- };
-
- friend bool operator==(const FrameBufferUpdateRequest&,
- const FrameBufferUpdateRequest&);
- friend bool operator!=(const FrameBufferUpdateRequest&,
- const FrameBufferUpdateRequest&);
-
- bool closed();
- void SetupProtocol();
- void SetupSecurityType();
-
- void GetClientInit();
-
- void SendServerInit() EXCLUDES(m_);
- static Message MakeFrameBufferUpdateHeader(std::uint16_t num_stripes);
-
- static void AppendRawStripeHeader(Message* frame_buffer_update,
- const Stripe& stripe);
- void AppendRawStripe(Message* frame_buffer_update, const Stripe& stripe) const
- REQUIRES(m_);
- Message MakeRawFrameBufferUpdate(const StripePtrVec& stripes) const
- REQUIRES(m_);
-
- static void AppendJpegSize(Message* frame_buffer_update, size_t jpeg_size);
- static void AppendJpegStripeHeader(Message* frame_buffer_update,
- const Stripe& stripe);
- static void AppendJpegStripe(Message* frame_buffer_update,
- const Stripe& stripe);
- static Message MakeJpegFrameBufferUpdate(const StripePtrVec& stripes);
-
- Message MakeFrameBufferUpdate(const StripePtrVec& frame) REQUIRES(m_);
-
- void FrameBufferUpdateRequestHandler(bool aggressive) EXCLUDES(m_);
-
- void SendDesktopSizeUpdate() REQUIRES(m_);
-
- bool IsUrgent(const FrameBufferUpdateRequest& update_request) const;
- static StripeSeqNumber MostRecentStripeSeqNumber(const StripePtrVec& stripes);
-
- void HandleFramebufferUpdateRequest() EXCLUDES(m_);
-
- void HandleSetEncodings();
-
- void HandleSetPixelFormat();
-
- void HandlePointerEvent() EXCLUDES(m_);
-
- void UpdateAccelerometer(float x, float y, float z);
-
- struct Coordinates {
- float x;
- float y;
- float z;
- };
-
- Coordinates CoordinatesForOrientation(ScreenOrientation orientation) const;
-
- int ScreenWidth() const REQUIRES(m_);
-
- int ScreenHeight() const REQUIRES(m_);
-
- void SetScreenOrientation(ScreenOrientation orientation) EXCLUDES(m_);
-
- // Returns true if key is special and the screen was rotated.
- bool RotateIfIsRotationCommand(std::uint32_t key);
-
- void HandleKeyEvent();
-
- void HandleClientCutText();
-
- void NormalSession();
-
- mutable std::mutex m_;
- ClientSocket client_;
- bool control_key_down_ = false;
- bool meta_key_down_ = false;
- std::shared_ptr<VirtualInputs> virtual_inputs_{};
-
- FrameBufferUpdateRequest previous_update_request_{};
- BlackBoard* bb_;
- bool use_jpeg_compression_ GUARDED_BY(m_) = false;
-
- std::thread frame_buffer_request_handler_tid_;
- bool closed_ GUARDED_BY(m_){};
-
- PixelFormat pixel_format_ GUARDED_BY(m_) = {
- std::uint8_t{32}, // bits per pixel
- std::uint8_t{24}, // depth
- std::uint8_t{0}, // big_endian
- std::uint8_t{1}, // true_color
- std::uint16_t{0xff}, // red_max, (maxes not used when true color flag is 0)
- std::uint16_t{0xff}, // green_max
- std::uint16_t{0xff}, // blue_max
- std::uint8_t{0}, // red_shift (shifts not used when true color flag is 0)
- std::uint8_t{8}, // green_shift
- std::uint8_t{16}, // blue_shift
- };
-
- bool supports_desktop_size_encoding_ = false;
- ScreenOrientation current_orientation_ GUARDED_BY(m_) =
- ScreenOrientation::Portrait;
-
- bool client_is_old_ = false;
-};
-
-} // namespace vnc
-} // namespace cuttlefish
diff --git a/host/frontend/vnc_server/vnc_server.cpp b/host/frontend/vnc_server/vnc_server.cpp
deleted file mode 100644
index eff0d57..0000000
--- a/host/frontend/vnc_server/vnc_server.cpp
+++ /dev/null
@@ -1,65 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#include "host/frontend/vnc_server/vnc_server.h"
-
-#include <memory>
-
-#include <android-base/logging.h>
-#include "common/libs/utils/tcp_socket.h"
-#include "host/frontend/vnc_server/blackboard.h"
-#include "host/frontend/vnc_server/frame_buffer_watcher.h"
-#include "host/frontend/vnc_server/jpeg_compressor.h"
-#include "host/frontend/vnc_server/virtual_inputs.h"
-#include "host/frontend/vnc_server/vnc_client_connection.h"
-#include "host/frontend/vnc_server/vnc_utils.h"
-
-using cuttlefish::vnc::VncServer;
-
-VncServer::VncServer(int port, bool aggressive,
- cuttlefish::vnc::ScreenConnector& screen_connector,
- cuttlefish::confui::HostVirtualInput& confui_input)
- : server_(port),
- virtual_inputs_(VirtualInputs::Get(confui_input)),
- frame_buffer_watcher_{&bb_, screen_connector},
- aggressive_{aggressive} {}
-
-void VncServer::MainLoop() {
- while (true) {
- LOG(DEBUG) << "Awaiting connections";
- auto connection = server_.Accept();
- LOG(DEBUG) << "Accepted a client connection";
- StartClient(std::move(connection));
- }
-}
-
-void VncServer::StartClient(ClientSocket sock) {
- std::thread t(&VncServer::StartClientThread, this, std::move(sock));
- t.detach();
-}
-
-void VncServer::StartClientThread(ClientSocket sock) {
- // NOTE if VncServer is expected to be destroyed, we have a problem here.
- // All of the client threads will be pointing to the VncServer's
- // data members. In the current setup, if the VncServer is destroyed with
- // clients still running, the clients will all be left with dangling
- // pointers.
- frame_buffer_watcher_.IncClientCount();
- VncClientConnection client(std::move(sock), virtual_inputs_, &bb_,
- aggressive_);
- client.StartSession();
- frame_buffer_watcher_.DecClientCount();
-}
diff --git a/host/frontend/vnc_server/vnc_server.h b/host/frontend/vnc_server/vnc_server.h
deleted file mode 100644
index 2751fd1..0000000
--- a/host/frontend/vnc_server/vnc_server.h
+++ /dev/null
@@ -1,64 +0,0 @@
-#pragma once
-
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#include <memory>
-#include <string>
-#include <thread>
-#include <utility>
-
-#include "common/libs/utils/tcp_socket.h"
-#include "host/frontend/vnc_server/blackboard.h"
-#include "host/frontend/vnc_server/frame_buffer_watcher.h"
-#include "host/frontend/vnc_server/jpeg_compressor.h"
-#include "host/frontend/vnc_server/virtual_inputs.h"
-#include "host/frontend/vnc_server/vnc_client_connection.h"
-#include "host/frontend/vnc_server/vnc_utils.h"
-#include "host/libs/confui/host_mode_ctrl.h"
-#include "host/libs/confui/host_virtual_input.h"
-#include "host/libs/screen_connector/screen_connector.h"
-
-namespace cuttlefish {
-namespace vnc {
-
-class VncServer {
- public:
- explicit VncServer(int port, bool aggressive,
- ScreenConnector& screen_connector,
- cuttlefish::confui::HostVirtualInput& confui_input);
-
- VncServer(const VncServer&) = delete;
- VncServer& operator=(const VncServer&) = delete;
-
- [[noreturn]] void MainLoop();
-
- private:
- void StartClient(ClientSocket sock);
-
- void StartClientThread(ClientSocket sock);
-
- ServerSocket server_;
-
- std::shared_ptr<VirtualInputs> virtual_inputs_;
- BlackBoard bb_;
-
- FrameBufferWatcher frame_buffer_watcher_;
- bool aggressive_{};
-};
-
-} // namespace vnc
-} // namespace cuttlefish
diff --git a/host/frontend/vnc_server/vnc_utils.h b/host/frontend/vnc_server/vnc_utils.h
deleted file mode 100644
index 7ec19f7..0000000
--- a/host/frontend/vnc_server/vnc_utils.h
+++ /dev/null
@@ -1,90 +0,0 @@
-#pragma once
-
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#include <array>
-#include <cstdint>
-#include <memory>
-#include <utility>
-#include <vector>
-
-#include "common/libs/utils/size_utils.h"
-#include "common/libs/utils/tcp_socket.h"
-#include "host/libs/config/cuttlefish_config.h"
-#include "host/libs/screen_connector/screen_connector.h"
-
-namespace cuttlefish {
-namespace vnc {
-
-// TODO(haining) when the hwcomposer gives a sequence number type, use that
-// instead. It might just work to replace this class with a type alias
-// using StripeSeqNumber = whatever_the_hwcomposer_uses;
-class StripeSeqNumber {
- public:
- StripeSeqNumber() = default;
- explicit StripeSeqNumber(std::uint64_t t) : t_{t} {}
- bool operator<(const StripeSeqNumber& other) const { return t_ < other.t_; }
-
- bool operator<=(const StripeSeqNumber& other) const { return t_ <= other.t_; }
-
- private:
- std::uint64_t t_{};
-};
-
-constexpr int32_t kJpegMaxQualityEncoding = -23;
-constexpr int32_t kJpegMinQualityEncoding = -32;
-
-enum class ScreenOrientation { Portrait, Landscape };
-constexpr int kNumOrientations = 2;
-
-struct Stripe {
- int index = -1;
- std::uint64_t frame_id{};
- std::uint16_t x{};
- std::uint16_t y{};
- std::uint16_t width{};
- std::uint16_t stride{};
- std::uint16_t height{};
- Message raw_data{};
- Message jpeg_data{};
- StripeSeqNumber seq_number{};
- ScreenOrientation orientation{};
-};
-
-/**
- * ScreenConnectorImpl will generate this, and enqueue
- *
- * It's basically a (processed) frame, so it:
- * must be efficiently std::move-able
- * Also, for the sake of algorithm simplicity:
- * must be default-constructable & assignable
- *
- */
-struct VncScProcessedFrame : public ScreenConnectorFrameInfo {
- Message raw_screen_;
- std::deque<Stripe> stripes_;
- std::unique_ptr<VncScProcessedFrame> Clone() {
- VncScProcessedFrame* cloned_frame = new VncScProcessedFrame();
- cloned_frame->raw_screen_ = raw_screen_;
- cloned_frame->stripes_ = stripes_;
- return std::unique_ptr<VncScProcessedFrame>(cloned_frame);
- }
-};
-using ScreenConnector = cuttlefish::ScreenConnector<VncScProcessedFrame>;
-
-} // namespace vnc
-} // namespace cuttlefish
diff --git a/host/frontend/webrtc/Android.bp b/host/frontend/webrtc/Android.bp
index 42f7fa4..d4384c5 100644
--- a/host/frontend/webrtc/Android.bp
+++ b/host/frontend/webrtc/Android.bp
@@ -31,7 +31,7 @@
"lib/utils.cpp",
"lib/video_track_source_impl.cpp",
"lib/vp8only_encoder_factory.cpp",
- "lib/ws_connection.cpp",
+ "lib/server_connection.cpp",
],
cflags: [
// libwebrtc headers need this
@@ -62,10 +62,12 @@
"libwebrtc_absl_types",
],
shared_libs: [
- "libssl",
"libbase",
+ "libcn-cbor",
"libcuttlefish_fs",
+ "libfruit",
"libjsoncpp",
+ "libssl",
"libwebm_mkvmuxer",
],
defaults: ["cuttlefish_buildhost_only"],
@@ -77,6 +79,7 @@
"adb_handler.cpp",
"audio_handler.cpp",
"bluetooth_handler.cpp",
+ "client_server.cpp",
"connection_observer.cpp",
"cvd_video_frame_buffer.cpp",
"display_handler.cpp",
@@ -102,13 +105,15 @@
"libwebrtc_absl_types",
"libaom",
"libcap",
+ "libcn-cbor",
"libcuttlefish_audio_connector",
+ "libcuttlefish_confui",
+ "libcuttlefish_confui_host",
"libcuttlefish_host_config",
+ "libcuttlefish_security",
"libcuttlefish_screen_connector",
"libcuttlefish_utils",
"libcuttlefish_wayland_server",
- "libcuttlefish_confui",
- "libcuttlefish_confui_host",
"libft2.nodep",
"libteeui",
"libteeui_localization",
@@ -128,11 +133,14 @@
"libyuv",
],
shared_libs: [
+ "libext2_blkid",
+ "[email protected]",
"libbase",
"libcrypto",
"libcuttlefish_fs",
"libcuttlefish_kernel_log_monitor_utils",
"libjsoncpp",
+ "libfruit",
"libopus",
"libssl",
"libvpx",
@@ -141,3 +149,59 @@
],
defaults: ["cuttlefish_buildhost_only"],
}
+
+prebuilt_usr_share_host {
+ name: "webrtc_client.html",
+ src: "client/client.html",
+ filename: "client.html",
+ sub_dir: "webrtc/assets",
+}
+
+prebuilt_usr_share_host {
+ name: "webrtc_style.css",
+ src: "client/style.css",
+ filename: "style.css",
+ sub_dir: "webrtc/assets",
+}
+
+prebuilt_usr_share_host {
+ name: "webrtc_controls.css",
+ src: "client/controls.css",
+ filename: "controls.css",
+ sub_dir: "webrtc/assets",
+}
+
+prebuilt_usr_share_host {
+ name: "webrtc_adb.js",
+ src: "client/js/adb.js",
+ filename: "adb.js",
+ sub_dir: "webrtc/assets/js",
+}
+
+prebuilt_usr_share_host {
+ name: "webrtc_cf.js",
+ src: "client/js/cf_webrtc.js",
+ filename: "cf_webrtc.js",
+ sub_dir: "webrtc/assets/js",
+}
+
+prebuilt_usr_share_host {
+ name: "webrtc_app.js",
+ src: "client/js/app.js",
+ filename: "app.js",
+ sub_dir: "webrtc/assets/js",
+}
+
+prebuilt_usr_share_host {
+ name: "webrtc_controls.js",
+ src: "client/js/controls.js",
+ filename: "controls.js",
+ sub_dir: "webrtc/assets/js",
+}
+
+prebuilt_usr_share_host {
+ name: "webrtc_rootcanal.js",
+ src: "client/js/rootcanal.js",
+ filename: "rootcanal.js",
+ sub_dir: "webrtc/assets/js",
+}
diff --git a/host/frontend/webrtc/adb_handler.cpp b/host/frontend/webrtc/adb_handler.cpp
index c580f65..7ff55fb 100644
--- a/host/frontend/webrtc/adb_handler.cpp
+++ b/host/frontend/webrtc/adb_handler.cpp
@@ -44,7 +44,11 @@
}
auto local_client = SharedFD::SocketLocalClient(port, SOCK_STREAM);
- CHECK(local_client->IsOpen()) << "Failed to connect to adb socket: " << local_client->StrError();
+ if (!local_client->IsOpen()) {
+ LOG(WARNING) << "Failed to connect to ADB server socket (non-Android guest?) Using /dev/null workaround."
+ << local_client->StrError();
+ return SharedFD::Open("/dev/null", O_RDWR);
+ }
return local_client;
}
diff --git a/host/frontend/webrtc/audio_handler.cpp b/host/frontend/webrtc/audio_handler.cpp
index 1cd8938..52a2294 100644
--- a/host/frontend/webrtc/audio_handler.cpp
+++ b/host/frontend/webrtc/audio_handler.cpp
@@ -484,18 +484,69 @@
buffer.SendStatus(AudioStatus::VIRTIO_SND_S_OK, 0, buffer.len());
return;
}
- auto bytes_per_sample = stream_desc.bits_per_sample / 8;
- auto samples_per_channel =
- buffer.len() / stream_desc.channels / bytes_per_sample;
+ const auto bytes_per_sample = stream_desc.bits_per_sample / 8;
+ const auto samples_per_channel = stream_desc.sample_rate / 100;
+ const auto bytes_per_request =
+ samples_per_channel * bytes_per_sample * stream_desc.channels;
bool muted = false;
- auto res = audio_source_->GetMoreAudioData(
- const_cast<uint8_t*>(buffer.get()), bytes_per_sample,
- samples_per_channel, stream_desc.channels, stream_desc.sample_rate,
- muted);
- if (res < 0) {
- // This is likely a recoverable error, log the error but don't let the VMM
- // know about it so that it doesn't crash.
- LOG(ERROR) << "Failed to receive audio data from client";
+ size_t bytes_read = 0;
+ auto& holding_buffer = stream_descs_[stream_id].buffer;
+ auto rx_buffer = const_cast<uint8_t*>(buffer.get());
+ if (!holding_buffer.empty()) {
+ // Consume any bytes remaining from previous requests
+ bytes_read += holding_buffer.Take(rx_buffer + bytes_read,
+ buffer.len() - bytes_read);
+ }
+ while (buffer.len() - bytes_read >= bytes_per_request) {
+ // Skip the holding buffer in as many reads as possible to avoid the extra
+ // copies
+ auto write_pos = rx_buffer + bytes_read;
+ auto res = audio_source_->GetMoreAudioData(
+ write_pos, bytes_per_sample, samples_per_channel,
+ stream_desc.channels, stream_desc.sample_rate, muted);
+ if (res < 0) {
+ // This is likely a recoverable error, log the error but don't let the
+ // VMM know about it so that it doesn't crash.
+ LOG(ERROR) << "Failed to receive audio data from client";
+ break;
+ }
+ if (muted) {
+ // The source is muted, just fill the buffer with zeros and return
+ memset(rx_buffer + bytes_read, 0, buffer.len() - bytes_read);
+ bytes_read = buffer.len();
+ break;
+ }
+ auto bytes_received = res * bytes_per_sample * stream_desc.channels;
+ bytes_read += bytes_received;
+ }
+ if (bytes_read < buffer.len()) {
+ // There is some buffer left to fill, but it's less than 10ms, read into
+ // holding buffer to ensure the remainder is kept around for future reads
+ auto write_pos = holding_buffer.data();
+ // Holding buffer is the exact size we need to read into and is emptied
+ // before we try to read into it.
+ CHECK(holding_buffer.freeCapacity() >= bytes_per_request)
+ << "Buffer too small for receiving audio";
+ auto res = audio_source_->GetMoreAudioData(
+ write_pos, bytes_per_sample, samples_per_channel,
+ stream_desc.channels, stream_desc.sample_rate, muted);
+ if (res < 0) {
+ // This is likely a recoverable error, log the error but don't let the
+ // VMM know about it so that it doesn't crash.
+ LOG(ERROR) << "Failed to receive audio data from client";
+ } else if (muted) {
+ // The source is muted, just fill the buffer with zeros and return
+ memset(rx_buffer + bytes_read, 0, buffer.len() - bytes_read);
+ bytes_read = buffer.len();
+ } else {
+ auto bytes_received = res * bytes_per_sample * stream_desc.channels;
+ holding_buffer.count += bytes_received;
+ bytes_read += holding_buffer.Take(rx_buffer + bytes_read,
+ buffer.len() - bytes_read);
+ // If the entire buffer is not full by now there is a bug above
+ // somewhere
+ CHECK(bytes_read == buffer.len()) << "Failed to read entire buffer";
+ }
}
}
buffer.SendStatus(AudioStatus::VIRTIO_SND_S_OK, 0, buffer.len());
@@ -514,12 +565,24 @@
return added_len;
}
+size_t AudioHandler::HoldingBuffer::Take(uint8_t* dst, size_t len) {
+ auto n = std::min(len, count);
+ std::copy(buffer.begin(), buffer.begin() + n, dst);
+ std::copy(buffer.begin() + n, buffer.begin() + count, buffer.begin());
+ count -= n;
+ return n;
+}
+
bool AudioHandler::HoldingBuffer::empty() const { return count == 0; }
bool AudioHandler::HoldingBuffer::full() const {
return count == buffer.size();
}
+size_t AudioHandler::HoldingBuffer::freeCapacity() const {
+ return buffer.size() - count;
+}
+
uint8_t* AudioHandler::HoldingBuffer::data() { return buffer.data(); }
} // namespace cuttlefish
diff --git a/host/frontend/webrtc/audio_handler.h b/host/frontend/webrtc/audio_handler.h
index 4da645c..fc4734a 100644
--- a/host/frontend/webrtc/audio_handler.h
+++ b/host/frontend/webrtc/audio_handler.h
@@ -35,9 +35,12 @@
void Reset(size_t size);
size_t Add(const volatile uint8_t* data, size_t max_len);
+ size_t Take(uint8_t* dst, size_t len);
bool empty() const;
bool full() const;
+ size_t freeCapacity() const;
uint8_t* data();
+ uint8_t* end();
};
struct StreamDesc {
std::mutex mtx;
diff --git a/host/frontend/webrtc/client/client.html b/host/frontend/webrtc/client/client.html
new file mode 100644
index 0000000..3ffb97b
--- /dev/null
+++ b/host/frontend/webrtc/client/client.html
@@ -0,0 +1,158 @@
+<?--
+ Copyright (C) 2019 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ -->
+
+<html>
+ <head>
+ <link rel="stylesheet" type="text/css" href="style.css" >
+ <link rel="stylesheet" type="text/css" href="controls.css" >
+ <link rel="stylesheet" href="https://fonts.googleapis.com/icon?family=Material+Icons+Outlined">
+ <link rel="stylesheet" href="https://fonts.googleapis.com/icon?family=Material+Icons">
+ <script src="https://ajax.googleapis.com/ajax/libs/jquery/3.6.0/jquery.min.js"></script>
+ </head>
+
+ <body>
+ <div id="loader"></div>
+ <div id="error-message-div">
+ <h3 id="error-message" class="hidden">
+ <span class="material-icons close-btn">close</span>
+ </h3>
+ </div>
+ <section id="device-connection">
+ <div id='header'>
+ <div id='app-controls'>
+ <div id="keyboard-capture-control" title="Capture Keyboard"></div>
+ <div id="mic-capture-control" title="Capture Microphone"></div>
+ <div id="camera-control" title="Capture Camera"></div>
+ <div id="audio-playback-control" title="Play audio">
+ <audio id="device-audio"></audio>
+ </div>
+ <div id="record-video-control" title="Capture Display as Webm"></div>
+ </div>
+ <div id='status-div'>
+ <h3 id='status-message' class='connecting'>Connecting to device</h3>
+ </div>
+ </div>
+ <div id='controls-and-displays'>
+ <div id='control-panel-default-buttons' class='control-panel-column'>
+ <button id='device-details-button' title='Device Details' class='material-icons'>
+ settings
+ </button>
+ <button id='bluetooth-modal-button' title='Bluetooth console' class='material-icons'>
+ settings_bluetooth
+ </button>
+ </div>
+ <div id='control-panel-custom-buttons' class='control-panel-column'></div>
+ <div id='device-displays'>
+ </div>
+ </div>
+ </section>
+ <div id='device-details-modal' class='modal'>
+ <div id='device-details-modal-header' class='modal-header'>
+ <h2>Device Details</h2>
+ <button id='device-details-close' title='Close' class='material-icons modal-close'>close</button>
+ </div>
+ <hr>
+ <h3>Hardware Configuration</h3>
+ <span id='device-details-hardware'>unknown</span>
+ </div>
+
+ <div id='bluetooth-modal' class='modal-wrapper'>
+ <div id='bluetooth-prompt' class='modal'>
+ <div id='bluetooth-prompt-header' class='modal-header'>
+ <h2>Bluetooth</h2>
+ <button id='bluetooth-prompt-close' title='Close' class='material-icons modal-close'>close</button>
+ </div>
+ <div>
+ <div id='bluetooth-prompt-text' class='bluetooth-text'>
+ We have enabled a BT Wizard to simplify adding a<br>bluetooth device.<br>
+ Alternatively, you can enter the BT Console if you<br>want to exercise full control.</div><br>
+ <div class='bluetooth-button'>
+ <button id='bluetooth-prompt-wizard' title='Start Wizard' class='modal-button-highlight'>Start Wizard</button>
+ <button id='bluetooth-prompt-list' title='Device List' class='modal-button'>Device List</button>
+ <button id='bluetooth-prompt-console' title='BT Console' class='modal-button'>BT Console</button>
+ </div>
+ </div>
+ </div>
+ <div id='bluetooth-wizard' class='modal'>
+ <div id='bluetooth-wizard-modal-header' class='modal-header'>
+ <h2>BT Wizard</h2>
+ <button id='bluetooth-wizard-close' title='Close' class='material-icons modal-close'>close</button>
+ </div>
+ <div>
+ <div class='bluetooth-text-field'><input type="text" id='bluetooth-wizard-name' placeholder="Device Name"></input></div>
+ <div class='bluetooth-drop-down'>
+ <select id='bluetooth-wizard-type' validate-mac="true" required>
+ <option value="beacon">Beacon</option>
+ <option value="beacon_swarm">Beacon Swarm</option>
+ <!-- Disabled because they were "started but never finished" (according to mylesgw@)
+ <option value="car_kit">Car Kit</option>
+ <option value="classic">Classic</option> -->
+ <option value="keyboard">Keyboard</option>
+ <option value="remote_loopback">Remote Loopback</option>
+ <option value="scripted_beacon">Scripted Beacon</option>
+ <!-- Disabled because it will never show up in the UI
+ <option value="sniffer">Sniffer</option> -->
+ </select>
+ </div>
+ <div class='bluetooth-text-field'><input type="text" id='bluetooth-wizard-mac' placeholder="Device MAC" validate-mac="true" required></input><span></span></div>
+ <div class='bluetooth-button'>
+ <button id='bluetooth-wizard-device' title='Add Device' class='modal-button-highlight' disabled>Add Device</button>
+ <button id='bluetooth-wizard-cancel' title='Cancel' class='modal-button'>Cancel</button>
+ </div>
+ </div>
+ </div>
+ <div id='bluetooth-wizard-confirm' class='modal'>
+ <div id='bluetooth-wizard-confirm-header' class='modal-header'>
+ <h2>BT Wizard</h2>
+ <button id='bluetooth-wizard-confirm-close' title='Close' class='material-icons modal-close'>close</button>
+ </div>
+ <div id='bluetooth-wizard-text' class='bluetooth-text'>Device added. See device details below.</div><br>
+ <div class='bluetooth-text'>
+ <p>Name: <b>GKeyboard</b></p>
+ <p>Type: <b>Keyboard</b></p>
+ <p>MAC Addr: <b>be:ac:01:55:00:03</b></p>
+ </div>
+ <div class='bluetooth-button'><button id='bluetooth-wizard-another' title='Add Another' class='modal-button-highlight'>Add Another</button></div>
+ </div>
+ <div id='bluetooth-list' class='modal'>
+ <div id='bluetooth-list-header' class='modal-header'>
+ <h2>Device List</h2>
+ <button id='bluetooth-list-close' title='Close' class='material-icons modal-close'>close</button>
+ </div>
+ <div class='bluetooth-text'>
+ <div><button title="Delete" data-device-id="delete" class="bluetooth-list-trash material-icons">delete</button>GKeyboard | Keyboard | be:ac:01:55:00:03</div>
+ <div><button title="Delete" data-device-id="delete" class="bluetooth-list-trash material-icons">delete</button>GHeadphones | Audio | dc:fa:32:00:55:02</div>
+ </div>
+ </div>
+ <div id='bluetooth-console' class='modal'>
+ <div id='bluetooth-console-modal-header' class='modal-header'>
+ <h2>BT Console</h2>
+ <button id='bluetooth-console-close' title='Close' class='material-icons modal-close'>close</button>
+ </div>
+ <div>
+ <div colspan='2'><textarea id='bluetooth-console-view' readonly rows='10' cols='60'></textarea></div>
+ <div width='1'><p id='bluetooth-console-cmd-label'>Command:</p></div>
+ <div width='100'><input id='bluetooth-console-input' type='text'></input></div>
+ </div>
+ </div>
+ </div>
+ <script src="js/adb.js"></script>
+ <script src="js/rootcanal.js"></script>
+ <script src="js/cf_webrtc.js" type="module"></script>
+ <script src="js/controls.js"></script>
+ <script src="js/app.js"></script>
+ </body>
+</html>
diff --git a/host/frontend/webrtc_operator/assets/controls.css b/host/frontend/webrtc/client/controls.css
similarity index 100%
rename from host/frontend/webrtc_operator/assets/controls.css
rename to host/frontend/webrtc/client/controls.css
diff --git a/host/frontend/webrtc/client/js/adb.js b/host/frontend/webrtc/client/js/adb.js
new file mode 100644
index 0000000..b011114
--- /dev/null
+++ b/host/frontend/webrtc/client/js/adb.js
@@ -0,0 +1,201 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+let adb_ws;
+
+let utf8Encoder = new TextEncoder();
+let utf8Decoder = new TextDecoder();
+
+const A_CNXN = 0x4e584e43;
+const A_OPEN = 0x4e45504f;
+const A_WRTE = 0x45545257;
+const A_OKAY = 0x59414b4f;
+
+const kLocalChannelId = 666;
+
+let array = new Uint8Array();
+
+function setU32LE(array, offset, x) {
+ array[offset] = x & 0xff;
+ array[offset + 1] = (x >> 8) & 0xff;
+ array[offset + 2] = (x >> 16) & 0xff;
+ array[offset + 3] = x >> 24;
+}
+
+function getU32LE(array, offset) {
+ let x = array[offset] | (array[offset + 1] << 8) | (array[offset + 2] << 16) |
+ (array[offset + 3] << 24);
+
+ return x >>> 0; // convert signed to unsigned if necessary.
+}
+
+function computeChecksum(array) {
+ let sum = 0;
+ let i;
+ for (i = 0; i < array.length; ++i) {
+ sum = ((sum + array[i]) & 0xffffffff) >>> 0;
+ }
+
+ return sum;
+}
+
+function createAdbMessage(command, arg0, arg1, payload) {
+ let arrayBuffer = new ArrayBuffer(24 + payload.length);
+ let array = new Uint8Array(arrayBuffer);
+ setU32LE(array, 0, command);
+ setU32LE(array, 4, arg0);
+ setU32LE(array, 8, arg1);
+ setU32LE(array, 12, payload.length);
+ setU32LE(array, 16, computeChecksum(payload));
+ setU32LE(array, 20, command ^ 0xffffffff);
+ array.set(payload, 24);
+
+ return arrayBuffer;
+}
+
+function adbOpenConnection() {
+ let systemIdentity = utf8Encoder.encode('Cray_II:1234:whatever');
+
+ let arrayBuffer =
+ createAdbMessage(A_CNXN, 0x1000000, 256 * 1024, systemIdentity);
+
+ adb_ws.send(arrayBuffer);
+}
+
+function adbShell(command) {
+ let destination = utf8Encoder.encode('shell:' + command);
+
+ let arrayBuffer = createAdbMessage(A_OPEN, kLocalChannelId, 0, destination);
+ adb_ws.send(arrayBuffer);
+ awaitConnection();
+}
+
+function adbSendOkay(remoteId) {
+ let payload = new Uint8Array(0);
+
+ let arrayBuffer =
+ createAdbMessage(A_OKAY, kLocalChannelId, remoteId, payload);
+
+ adb_ws.send(arrayBuffer);
+}
+
+function JoinArrays(arr1, arr2) {
+ let arr = new Uint8Array(arr1.length + arr2.length);
+ arr.set(arr1, 0);
+ arr.set(arr2, arr1.length);
+ return arr;
+}
+
+// Simple lifecycle management that executes callbacks based on connection
+// state.
+//
+// Any attempt to initiate a command (e.g. creating a connection, sending a
+// message) (re)starts a timer. Any response back from any command stops that
+// timer.
+const timeoutMs = 3000;
+let connectedCb;
+let disconnectedCb;
+let disconnectedTimeout;
+function awaitConnection() {
+ clearTimeout(disconnectedTimeout);
+ if (disconnectedCb) {
+ disconnectedTimeout = setTimeout(disconnectedCb, timeoutMs);
+ }
+}
+function connected() {
+ if (disconnectedTimeout) {
+ clearTimeout(disconnectedTimeout);
+ }
+ if (connectedCb) {
+ connectedCb();
+ }
+}
+
+function adbOnMessage(arrayBuffer) {
+ // console.debug("adb_ws: onmessage (" + arrayBuffer.byteLength + " bytes)");
+ array = JoinArrays(array, new Uint8Array(arrayBuffer));
+
+ while (array.length > 0) {
+ if (array.length < 24) {
+ // Incomplete package, must wait for more data.
+ return;
+ }
+
+ let command = getU32LE(array, 0);
+ let magic = getU32LE(array, 20);
+
+ if (command != ((magic ^ 0xffffffff) >>> 0)) {
+ console.error('adb message command vs magic failed.');
+ console.error('command = ' + command + ', magic = ' + magic);
+ return;
+ }
+
+ let payloadLength = getU32LE(array, 12);
+
+ if (array.length < 24 + payloadLength) {
+ // Incomplete package, must wait for more data.
+ return;
+ }
+
+ let payloadChecksum = getU32LE(array, 16);
+ let checksum = computeChecksum(array.slice(24));
+
+ if (payloadChecksum != checksum) {
+ console.error('adb message checksum mismatch.');
+ // This can happen if a shell command executes while another
+ // channel is receiving data.
+ }
+
+ switch (command) {
+ case A_CNXN: {
+ console.info('WebRTC adb connected.');
+ connected();
+ break;
+ }
+
+ case A_OKAY: {
+ let remoteId = getU32LE(array, 4);
+ console.debug('WebRTC adb channel created w/ remoteId ' + remoteId);
+ connected();
+ break;
+ }
+
+ case A_WRTE: {
+ let remoteId = getU32LE(array, 4);
+ adbSendOkay(remoteId);
+ break;
+ }
+ }
+ array = array.subarray(24 + payloadLength, array.length);
+ }
+}
+
+function init_adb(devConn, ccb = connectedCb, dcb = disconnectedCb) {
+ if (adb_ws) return;
+
+ adb_ws = {
+ send: function(buffer) {
+ devConn.sendAdbMessage(buffer);
+ }
+ };
+ connectedCb = ccb;
+ disconnectedCb = dcb;
+ awaitConnection();
+
+ devConn.onAdbMessage(msg => adbOnMessage(msg));
+
+ adbOpenConnection();
+}
diff --git a/host/frontend/webrtc/client/js/app.js b/host/frontend/webrtc/client/js/app.js
new file mode 100644
index 0000000..f26862a
--- /dev/null
+++ b/host/frontend/webrtc/client/js/app.js
@@ -0,0 +1,1031 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+'use strict';
+
+async function ConnectDevice(deviceId, serverConnector) {
+ console.debug('Connect: ' + deviceId);
+ // Prepare messages in case of connection failure
+ let connectionAttemptDuration = 0;
+ const intervalMs = 15000;
+ let connectionInterval = setInterval(() => {
+ connectionAttemptDuration += intervalMs;
+ if (connectionAttemptDuration > 30000) {
+ showError(
+ 'Connection should have occurred by now. ' +
+ 'Please attempt to restart the guest device.');
+ clearInterval(connectionInterval);
+ } else if (connectionAttemptDuration > 15000) {
+ showWarning('Connection is taking longer than expected');
+ }
+ }, intervalMs);
+
+ let module = await import('./cf_webrtc.js');
+ let deviceConnection = await module.Connect(deviceId, serverConnector);
+ console.info('Connected to ' + deviceId);
+ clearInterval(connectionInterval);
+ return deviceConnection;
+}
+
+function setupMessages() {
+ let closeBtn = document.querySelector('#error-message .close-btn');
+ closeBtn.addEventListener('click', evt => {
+ evt.target.parentElement.className = 'hidden';
+ });
+}
+
+function showMessage(msg, className) {
+ let element = document.getElementById('error-message');
+ if (element.childNodes.length < 2) {
+ // First time, no text node yet
+ element.insertAdjacentText('afterBegin', msg);
+ } else {
+ element.childNodes[0].data = msg;
+ }
+ element.className = className;
+}
+
+function showWarning(msg) {
+ showMessage(msg, 'warning');
+}
+
+function showError(msg) {
+ showMessage(msg, 'error');
+}
+
+
+class DeviceDetailsUpdater {
+ #element;
+
+ constructor() {
+ this.#element = document.getElementById('device-details-hardware');
+ }
+
+ setHardwareDetailsText(text) {
+ this.#element.dataset.hardwareDetailsText = text;
+ return this;
+ }
+
+ setDeviceStateDetailsText(text) {
+ this.#element.dataset.deviceStateDetailsText = text;
+ return this;
+ }
+
+ update() {
+ this.#element.textContent =
+ [
+ this.#element.dataset.hardwareDetailsText,
+ this.#element.dataset.deviceStateDetailsText,
+ ].filter(e => e /*remove empty*/)
+ .join('\n');
+ }
+} // DeviceDetailsUpdater
+
+class DeviceControlApp {
+ #deviceConnection = {};
+ #currentRotation = 0;
+ #displayDescriptions = [];
+ #buttons = {};
+ #recording = {};
+ #phys = {};
+ #deviceCount = 0;
+
+ constructor(deviceConnection) {
+ this.#deviceConnection = deviceConnection;
+ }
+
+ start() {
+ console.debug('Device description: ', this.#deviceConnection.description);
+ this.#deviceConnection.onControlMessage(msg => this.#onControlMessage(msg));
+ createToggleControl(
+ document.getElementById('keyboard-capture-control'), 'keyboard',
+ enabled => this.#onKeyboardCaptureToggle(enabled));
+ createToggleControl(
+ document.getElementById('mic-capture-control'), 'mic',
+ enabled => this.#onMicCaptureToggle(enabled));
+ createToggleControl(
+ document.getElementById('camera-control'), 'videocam',
+ enabled => this.#onCameraCaptureToggle(enabled));
+ createToggleControl(
+ document.getElementById('record-video-control'), 'movie_creation',
+ enabled => this.#onVideoCaptureToggle(enabled));
+ const audioElm = document.getElementById('device-audio');
+
+ let audioPlaybackCtrl = createToggleControl(
+ document.getElementById('audio-playback-control'), 'speaker',
+ enabled => this.#onAudioPlaybackToggle(enabled), !audioElm.paused);
+ // The audio element may start or stop playing at any time, this ensures the
+ // audio control always show the right state.
+ audioElm.onplay = () => audioPlaybackCtrl.Set(true);
+ audioElm.onpause = () => audioPlaybackCtrl.Set(false);
+
+ this.#showDeviceUI();
+ }
+
+ #showDeviceUI() {
+ window.onresize = evt => this.#resizeDeviceDisplays();
+ // Set up control panel buttons
+ this.#buttons = {};
+ this.#buttons['power'] = createControlPanelButton(
+ 'power', 'Power', 'power_settings_new',
+ evt => this.#onControlPanelButton(evt));
+ this.#buttons['home'] = createControlPanelButton(
+ 'home', 'Home', 'home', evt => this.#onControlPanelButton(evt));
+ this.#buttons['menu'] = createControlPanelButton(
+ 'menu', 'Menu', 'menu', evt => this.#onControlPanelButton(evt));
+ this.#buttons['rotate'] = createControlPanelButton(
+ 'rotate', 'Rotate', 'screen_rotation',
+ evt => this.#onRotateButton(evt));
+ this.#buttons['rotate'].adb = true;
+ this.#buttons['volumedown'] = createControlPanelButton(
+ 'volumedown', 'Volume Down', 'volume_down',
+ evt => this.#onControlPanelButton(evt));
+ this.#buttons['volumeup'] = createControlPanelButton(
+ 'volumeup', 'Volume Up', 'volume_up',
+ evt => this.#onControlPanelButton(evt));
+
+ createModalButton(
+ 'device-details-button', 'device-details-modal',
+ 'device-details-close');
+ createModalButton(
+ 'bluetooth-modal-button', 'bluetooth-prompt',
+ 'bluetooth-prompt-close');
+ createModalButton(
+ 'bluetooth-prompt-wizard', 'bluetooth-wizard',
+ 'bluetooth-wizard-close', 'bluetooth-prompt');
+ createModalButton(
+ 'bluetooth-wizard-device', 'bluetooth-wizard-confirm',
+ 'bluetooth-wizard-confirm-close', 'bluetooth-wizard');
+ createModalButton(
+ 'bluetooth-wizard-another', 'bluetooth-wizard',
+ 'bluetooth-wizard-close', 'bluetooth-wizard-confirm');
+ createModalButton(
+ 'bluetooth-prompt-list', 'bluetooth-list',
+ 'bluetooth-list-close', 'bluetooth-prompt');
+ createModalButton(
+ 'bluetooth-prompt-console', 'bluetooth-console',
+ 'bluetooth-console-close', 'bluetooth-prompt');
+ createModalButton(
+ 'bluetooth-wizard-cancel', 'bluetooth-prompt',
+ 'bluetooth-wizard-close', 'bluetooth-wizard');
+
+ positionModal('device-details-button', 'bluetooth-modal');
+ positionModal('device-details-button', 'bluetooth-prompt');
+ positionModal('device-details-button', 'bluetooth-wizard');
+ positionModal('device-details-button', 'bluetooth-wizard-confirm');
+ positionModal('device-details-button', 'bluetooth-list');
+ positionModal('device-details-button', 'bluetooth-console');
+
+ createButtonListener('bluetooth-prompt-list', null, this.#deviceConnection,
+ evt => this.#onRootCanalCommand(this.#deviceConnection, "list", evt));
+ createButtonListener('bluetooth-wizard-device', null, this.#deviceConnection,
+ evt => this.#onRootCanalCommand(this.#deviceConnection, "add", evt));
+ createButtonListener('bluetooth-list-trash', null, this.#deviceConnection,
+ evt => this.#onRootCanalCommand(this.#deviceConnection, "del", evt));
+ createButtonListener('bluetooth-prompt-wizard', null, this.#deviceConnection,
+ evt => this.#onRootCanalCommand(this.#deviceConnection, "list", evt));
+ createButtonListener('bluetooth-wizard-another', null, this.#deviceConnection,
+ evt => this.#onRootCanalCommand(this.#deviceConnection, "list", evt));
+
+ if (this.#deviceConnection.description.custom_control_panel_buttons.length >
+ 0) {
+ document.getElementById('control-panel-custom-buttons').style.display =
+ 'flex';
+ for (const button of this.#deviceConnection.description
+ .custom_control_panel_buttons) {
+ if (button.shell_command) {
+ // This button's command is handled by sending an ADB shell command.
+ this.#buttons[button.command] = createControlPanelButton(
+ button.command, button.title, button.icon_name,
+ e => this.#onCustomShellButton(button.shell_command, e),
+ 'control-panel-custom-buttons');
+ this.#buttons[button.command].adb = true;
+ } else if (button.device_states) {
+ // This button corresponds to variable hardware device state(s).
+ this.#buttons[button.command] = createControlPanelButton(
+ button.command, button.title, button.icon_name,
+ this.#getCustomDeviceStateButtonCb(button.device_states),
+ 'control-panel-custom-buttons');
+ for (const device_state of button.device_states) {
+ // hinge_angle is currently injected via an adb shell command that
+ // triggers a guest binary.
+ if ('hinge_angle_value' in device_state) {
+ this.#buttons[button.command].adb = true;
+ }
+ }
+ } else {
+ // This button's command is handled by custom action server.
+ this.#buttons[button.command] = createControlPanelButton(
+ button.command, button.title, button.icon_name,
+ evt => this.#onControlPanelButton(evt),
+ 'control-panel-custom-buttons');
+ }
+ }
+ }
+
+ // Set up displays
+ this.#createDeviceDisplays();
+
+ // Set up audio
+ const deviceAudio = document.getElementById('device-audio');
+ for (const audio_desc of this.#deviceConnection.description.audio_streams) {
+ let stream_id = audio_desc.stream_id;
+ this.#deviceConnection.getStream(stream_id)
+ .then(stream => {
+ deviceAudio.srcObject = stream;
+ let playPromise = deviceAudio.play();
+ if (playPromise !== undefined) {
+ playPromise.catch(error => {
+ showWarning(
+ 'Audio playback is disabled, click on the speaker control to activate it');
+ });
+ }
+ })
+ .catch(e => console.error('Unable to get audio stream: ', e));
+ }
+
+ // Set up touch input
+ this.#startMouseTracking();
+
+ this.#updateDeviceHardwareDetails(
+ this.#deviceConnection.description.hardware);
+
+ // Show the error message and disable buttons when the WebRTC connection
+ // fails.
+ this.#deviceConnection.onConnectionStateChange(state => {
+ if (state == 'disconnected' || state == 'failed') {
+ this.#showWebrtcError();
+ }
+ });
+
+ let bluetoothConsole =
+ cmdConsole('bluetooth-console-view', 'bluetooth-console-input');
+ bluetoothConsole.addCommandListener(cmd => {
+ let inputArr = cmd.split(' ');
+ let command = inputArr[0];
+ inputArr.shift();
+ let args = inputArr;
+ this.#deviceConnection.sendBluetoothMessage(
+ createRootcanalMessage(command, args));
+ });
+ this.#deviceConnection.onBluetoothMessage(msg => {
+ let decoded = decodeRootcanalMessage(msg);
+ let deviceCount = btUpdateDeviceList(decoded);
+ if (deviceCount > 0) {
+ this.#deviceCount = deviceCount;
+ createButtonListener('bluetooth-list-trash', null, this.#deviceConnection,
+ evt => this.#onRootCanalCommand(this.#deviceConnection, "del", evt));
+ }
+ btUpdateAdded(decoded);
+ let phyList = btParsePhys(decoded);
+ if (phyList) {
+ this.#phys = phyList;
+ }
+ bluetoothConsole.addLine(decoded);
+ });
+ }
+
+ #onRootCanalCommand(deviceConnection, cmd, evt) {
+ if (cmd == "list") {
+ deviceConnection.sendBluetoothMessage(createRootcanalMessage("list", []));
+ }
+ if (cmd == "del") {
+ let id = evt.srcElement.getAttribute("data-device-id");
+ deviceConnection.sendBluetoothMessage(createRootcanalMessage("del", [id]));
+ deviceConnection.sendBluetoothMessage(createRootcanalMessage("list", []));
+ }
+ if (cmd == "add") {
+ let name = document.getElementById('bluetooth-wizard-name').value;
+ let type = document.getElementById('bluetooth-wizard-type').value;
+ if (type == "remote_loopback") {
+ deviceConnection.sendBluetoothMessage(createRootcanalMessage("add", [type]));
+ } else {
+ let mac = document.getElementById('bluetooth-wizard-mac').value;
+ deviceConnection.sendBluetoothMessage(createRootcanalMessage("add", [type, mac]));
+ }
+ let phyId = this.#phys["LOW_ENERGY"].toString();
+ if (type == "remote_loopback") {
+ phyId = this.#phys["BR_EDR"].toString();
+ }
+ let devId = this.#deviceCount.toString();
+ this.#deviceCount++;
+ deviceConnection.sendBluetoothMessage(createRootcanalMessage("add_device_to_phy", [devId, phyId]));
+ }
+ }
+
+ #showWebrtcError() {
+ document.getElementById('status-message').className = 'error';
+ document.getElementById('status-message').textContent =
+ 'No connection to the guest device. ' +
+ 'Please ensure the WebRTC process on the host machine is active.';
+ document.getElementById('status-message').style.visibility = 'visible';
+ const deviceDisplays = document.getElementById('device-displays');
+ deviceDisplays.style.display = 'none';
+ for (const [_, button] of Object.entries(this.#buttons)) {
+ button.disabled = true;
+ }
+ }
+
+ #takePhoto() {
+ const imageCapture = this.#deviceConnection.imageCapture;
+ if (imageCapture) {
+ const photoSettings = {
+ imageWidth: this.#deviceConnection.cameraWidth,
+ imageHeight: this.#deviceConnection.cameraHeight
+ };
+ imageCapture.takePhoto(photoSettings)
+ .then(blob => blob.arrayBuffer())
+ .then(buffer => this.#deviceConnection.sendOrQueueCameraData(buffer))
+ .catch(error => console.error(error));
+ }
+ }
+
+ #getCustomDeviceStateButtonCb(device_states) {
+ let states = device_states;
+ let index = 0;
+ return e => {
+ if (e.type == 'mousedown') {
+ // Reset any overridden device state.
+ adbShell('cmd device_state state reset');
+ // Send a device_state message for the current state.
+ let message = {
+ command: 'device_state',
+ ...states[index],
+ };
+ this.#deviceConnection.sendControlMessage(JSON.stringify(message));
+ console.debug('Control message sent: ', JSON.stringify(message));
+ let lidSwitchOpen = null;
+ if ('lid_switch_open' in states[index]) {
+ lidSwitchOpen = states[index].lid_switch_open;
+ }
+ let hingeAngle = null;
+ if ('hinge_angle_value' in states[index]) {
+ hingeAngle = states[index].hinge_angle_value;
+ // TODO(b/181157794): Use a custom Sensor HAL for hinge_angle
+ // injection instead of this guest binary.
+ adbShell(
+ '/vendor/bin/cuttlefish_sensor_injection hinge_angle ' +
+ states[index].hinge_angle_value);
+ }
+ // Update the Device Details view.
+ this.#updateDeviceStateDetails(lidSwitchOpen, hingeAngle);
+ // Cycle to the next state.
+ index = (index + 1) % states.length;
+ }
+ }
+ }
+
+ #resizeDeviceDisplays() {
+ // Padding between displays.
+ const deviceDisplayWidthPadding = 10;
+ // Padding for the display info above each display video.
+ const deviceDisplayHeightPadding = 38;
+
+ let deviceDisplayList = document.getElementsByClassName('device-display');
+ let deviceDisplayVideoList =
+ document.getElementsByClassName('device-display-video');
+ let deviceDisplayInfoList =
+ document.getElementsByClassName('device-display-info');
+
+ const deviceDisplays = document.getElementById('device-displays');
+ const rotationDegrees = this.#getTransformRotation(deviceDisplays);
+ const rotationRadians = rotationDegrees * Math.PI / 180;
+
+ // Auto-scale the screen based on window size.
+ let availableWidth = deviceDisplays.clientWidth;
+ let availableHeight = deviceDisplays.clientHeight - deviceDisplayHeightPadding;
+
+ // Reserve space for padding between the displays.
+ availableWidth = availableWidth -
+ (this.#displayDescriptions.length * deviceDisplayWidthPadding);
+
+ // Loop once over all of the displays to compute the total space needed.
+ let neededWidth = 0;
+ let neededHeight = 0;
+ for (let i = 0; i < deviceDisplayList.length; i++) {
+ let deviceDisplayDescription = this.#displayDescriptions[i];
+ let deviceDisplayVideo = deviceDisplayVideoList[i];
+
+ const originalDisplayWidth = deviceDisplayDescription.x_res;
+ const originalDisplayHeight = deviceDisplayDescription.y_res;
+
+ const neededBoundingBoxWidth =
+ Math.abs(Math.cos(rotationRadians) * originalDisplayWidth) +
+ Math.abs(Math.sin(rotationRadians) * originalDisplayHeight);
+ const neededBoundingBoxHeight =
+ Math.abs(Math.sin(rotationRadians) * originalDisplayWidth) +
+ Math.abs(Math.cos(rotationRadians) * originalDisplayHeight);
+
+ neededWidth = neededWidth + neededBoundingBoxWidth;
+ neededHeight = Math.max(neededHeight, neededBoundingBoxHeight);
+ }
+
+ const scaling =
+ Math.min(availableWidth / neededWidth, availableHeight / neededHeight);
+
+ // Loop again over all of the displays to set the sizes and positions.
+ let deviceDisplayLeftOffset = 0;
+ for (let i = 0; i < deviceDisplayList.length; i++) {
+ let deviceDisplay = deviceDisplayList[i];
+ let deviceDisplayVideo = deviceDisplayVideoList[i];
+ let deviceDisplayInfo = deviceDisplayInfoList[i];
+ let deviceDisplayDescription = this.#displayDescriptions[i];
+
+ let rotated = this.#currentRotation == 1 ? ' (Rotated)' : '';
+ deviceDisplayInfo.textContent = `Display ${i} - ` +
+ `${deviceDisplayDescription.x_res}x` +
+ `${deviceDisplayDescription.y_res} ` +
+ `(${deviceDisplayDescription.dpi} DPI)${rotated}`;
+
+ const originalDisplayWidth = deviceDisplayDescription.x_res;
+ const originalDisplayHeight = deviceDisplayDescription.y_res;
+
+ const scaledDisplayWidth = originalDisplayWidth * scaling;
+ const scaledDisplayHeight = originalDisplayHeight * scaling;
+
+ const neededBoundingBoxWidth =
+ Math.abs(Math.cos(rotationRadians) * originalDisplayWidth) +
+ Math.abs(Math.sin(rotationRadians) * originalDisplayHeight);
+ const neededBoundingBoxHeight =
+ Math.abs(Math.sin(rotationRadians) * originalDisplayWidth) +
+ Math.abs(Math.cos(rotationRadians) * originalDisplayHeight);
+
+ const scaledBoundingBoxWidth = neededBoundingBoxWidth * scaling;
+ const scaledBoundingBoxHeight = neededBoundingBoxHeight * scaling;
+
+ const offsetX = (scaledBoundingBoxWidth - scaledDisplayWidth) / 2;
+ const offsetY = (scaledBoundingBoxHeight - scaledDisplayHeight) / 2;
+
+ deviceDisplayVideo.style.width = scaledDisplayWidth;
+ deviceDisplayVideo.style.height = scaledDisplayHeight;
+ deviceDisplayVideo.style.transform = `translateX(${offsetX}px) ` +
+ `translateY(${offsetY}px) ` +
+ `rotateZ(${rotationDegrees}deg) `;
+
+ deviceDisplay.style.left = `${deviceDisplayLeftOffset}px`;
+ deviceDisplay.style.width = scaledBoundingBoxWidth;
+ deviceDisplay.style.height = scaledBoundingBoxHeight;
+
+ deviceDisplayLeftOffset = deviceDisplayLeftOffset + deviceDisplayWidthPadding +
+ scaledBoundingBoxWidth;
+ }
+ }
+
+ #getTransformRotation(element) {
+ if (!element.style.textIndent) {
+ return 0;
+ }
+ // Remove 'px' and convert to float.
+ return parseFloat(element.style.textIndent.slice(0, -2));
+ }
+
+ #onControlMessage(message) {
+ let message_data = JSON.parse(message.data);
+ console.debug('Control message received: ', message_data)
+ let metadata = message_data.metadata;
+ if (message_data.event == 'VIRTUAL_DEVICE_BOOT_STARTED') {
+ // Start the adb connection after receiving the BOOT_STARTED message.
+ // (This is after the adbd start message. Attempting to connect
+ // immediately after adbd starts causes issues.)
+ this.#initializeAdb();
+ }
+ if (message_data.event == 'VIRTUAL_DEVICE_SCREEN_CHANGED') {
+ if (metadata.rotation != this.#currentRotation) {
+ // Animate the screen rotation.
+ const targetRotation = metadata.rotation == 0 ? 0 : -90;
+
+ $('#device-displays')
+ .animate(
+ {
+ textIndent: targetRotation,
+ },
+ {
+ duration: 1000,
+ step: (now, tween) => {
+ this.#resizeDeviceDisplays();
+ },
+ });
+ }
+
+ this.#currentRotation = metadata.rotation;
+ }
+ if (message_data.event == 'VIRTUAL_DEVICE_CAPTURE_IMAGE') {
+ if (this.#deviceConnection.cameraEnabled) {
+ this.#takePhoto();
+ }
+ }
+ if (message_data.event == 'VIRTUAL_DEVICE_DISPLAY_POWER_MODE_CHANGED') {
+ this.#updateDisplayVisibility(metadata.display, metadata.mode);
+ }
+ }
+
+ #updateDeviceStateDetails(lidSwitchOpen, hingeAngle) {
+ let deviceStateDetailsTextLines = [];
+ if (lidSwitchOpen != null) {
+ let state = lidSwitchOpen ? 'Opened' : 'Closed';
+ deviceStateDetailsTextLines.push(`Lid Switch - ${state}`);
+ }
+ if (hingeAngle != null) {
+ deviceStateDetailsTextLines.push(`Hinge Angle - ${hingeAngle}`);
+ }
+ let deviceStateDetailsText = deviceStateDetailsTextLines.join('\n');
+ new DeviceDetailsUpdater()
+ .setDeviceStateDetailsText(deviceStateDetailsText)
+ .update();
+ }
+
+ #updateDeviceHardwareDetails(hardware) {
+ let hardwareDetailsTextLines = [];
+ Object.keys(hardware).forEach((key) => {
+ let value = hardware[key];
+ hardwareDetailsTextLines.push(`${key} - ${value}`);
+ });
+
+ let hardwareDetailsText = hardwareDetailsTextLines.join('\n');
+ new DeviceDetailsUpdater()
+ .setHardwareDetailsText(hardwareDetailsText)
+ .update();
+ }
+
+ // Creates a <video> element and a <div> container element for each display.
+ // The extra <div> container elements are used to maintain the width and
+ // height of the device as the CSS 'transform' property used on the <video>
+ // element for rotating the device only affects the visuals of the element
+ // and not its layout.
+ #createDeviceDisplays() {
+ console.debug(
+ 'Display descriptions: ', this.#deviceConnection.description.displays);
+ this.#displayDescriptions = this.#deviceConnection.description.displays;
+ let anyDisplayLoaded = false;
+ const deviceDisplays = document.getElementById('device-displays');
+ for (const deviceDisplayDescription of this.#displayDescriptions) {
+ let deviceDisplay = document.createElement('div');
+ deviceDisplay.classList.add('device-display');
+ // Start the screen as hidden. Only show when data is ready.
+ deviceDisplay.style.visibility = 'hidden';
+
+ let deviceDisplayInfo = document.createElement("div");
+ deviceDisplayInfo.classList.add("device-display-info");
+ deviceDisplayInfo.id = deviceDisplayDescription.stream_id + '_info';
+ deviceDisplay.appendChild(deviceDisplayInfo);
+
+ let deviceDisplayVideo = document.createElement('video');
+ deviceDisplayVideo.autoplay = true;
+ deviceDisplayVideo.muted = true;
+ deviceDisplayVideo.id = deviceDisplayDescription.stream_id;
+ deviceDisplayVideo.classList.add('device-display-video');
+ deviceDisplayVideo.addEventListener('loadeddata', (evt) => {
+ if (!anyDisplayLoaded) {
+ anyDisplayLoaded = true;
+ this.#onDeviceDisplayLoaded();
+ }
+ });
+ deviceDisplay.appendChild(deviceDisplayVideo);
+
+ deviceDisplays.appendChild(deviceDisplay);
+
+ let stream_id = deviceDisplayDescription.stream_id;
+ this.#deviceConnection.getStream(stream_id)
+ .then(stream => {
+ deviceDisplayVideo.srcObject = stream;
+ })
+ .catch(e => console.error('Unable to get display stream: ', e));
+ }
+ }
+
+ #initializeAdb() {
+ init_adb(
+ this.#deviceConnection, () => this.#showAdbConnected(),
+ () => this.#showAdbError());
+ }
+
+ #showAdbConnected() {
+ // Screen changed messages are not reported until after boot has completed.
+ // Certain default adb buttons change screen state, so wait for boot
+ // completion before enabling these buttons.
+ document.getElementById('status-message').className = 'connected';
+ document.getElementById('status-message').textContent =
+ 'adb connection established successfully.';
+ setTimeout(() => {
+ document.getElementById('status-message').style.visibility = 'hidden';
+ }, 5000);
+ for (const [_, button] of Object.entries(this.#buttons)) {
+ if (button.adb) {
+ button.disabled = false;
+ }
+ }
+ }
+
+ #showAdbError() {
+ document.getElementById('status-message').className = 'error';
+ document.getElementById('status-message').textContent =
+ 'adb connection failed.';
+ document.getElementById('status-message').style.visibility = 'visible';
+ for (const [_, button] of Object.entries(this.#buttons)) {
+ if (button.adb) {
+ button.disabled = true;
+ }
+ }
+ }
+
+ #onDeviceDisplayLoaded() {
+ document.getElementById('status-message').textContent =
+ 'Awaiting bootup and adb connection. Please wait...';
+ this.#resizeDeviceDisplays();
+
+ let deviceDisplayList = document.getElementsByClassName('device-display');
+ for (const deviceDisplay of deviceDisplayList) {
+ deviceDisplay.style.visibility = 'visible';
+ }
+
+ // Enable the buttons after the screen is visible.
+ for (const [key, button] of Object.entries(this.#buttons)) {
+ if (!button.adb) {
+ button.disabled = false;
+ }
+ }
+ // Start the adb connection if it is not already started.
+ this.#initializeAdb();
+ }
+
+ #onRotateButton(e) {
+ // Attempt to init adb again, in case the initial connection failed.
+ // This succeeds immediately if already connected.
+ this.#initializeAdb();
+ if (e.type == 'mousedown') {
+ adbShell(
+ '/vendor/bin/cuttlefish_sensor_injection rotate ' +
+ (this.#currentRotation == 0 ? 'landscape' : 'portrait'))
+ }
+ }
+
+ #onControlPanelButton(e) {
+ if (e.type == 'mouseout' && e.which == 0) {
+ // Ignore mouseout events if no mouse button is pressed.
+ return;
+ }
+ this.#deviceConnection.sendControlMessage(JSON.stringify({
+ command: e.target.dataset.command,
+ button_state: e.type == 'mousedown' ? 'down' : 'up',
+ }));
+ }
+
+ #onKeyboardCaptureToggle(enabled) {
+ if (enabled) {
+ document.addEventListener('keydown', evt => this.#onKeyEvent(evt));
+ document.addEventListener('keyup', evt => this.#onKeyEvent(evt));
+ } else {
+ document.removeEventListener('keydown', evt => this.#onKeyEvent(evt));
+ document.removeEventListener('keyup', evt => this.#onKeyEvent(evt));
+ }
+ }
+
+ #onKeyEvent(e) {
+ e.preventDefault();
+ this.#deviceConnection.sendKeyEvent(e.code, e.type);
+ }
+
+ #startMouseTracking() {
+ let $this = this;
+ let mouseIsDown = false;
+ let mouseCtx = {
+ down: false,
+ touchIdSlotMap: new Map(),
+ touchSlots: [],
+ };
+ function onStartDrag(e) {
+ e.preventDefault();
+
+ // console.debug("mousedown at " + e.pageX + " / " + e.pageY);
+ mouseCtx.down = true;
+
+ $this.#sendEventUpdate(mouseCtx, e);
+ }
+
+ function onEndDrag(e) {
+ e.preventDefault();
+
+ // console.debug("mouseup at " + e.pageX + " / " + e.pageY);
+ mouseCtx.down = false;
+
+ $this.#sendEventUpdate(mouseCtx, e);
+ }
+
+ function onContinueDrag(e) {
+ e.preventDefault();
+
+ // console.debug("mousemove at " + e.pageX + " / " + e.pageY + ", down=" +
+ // mouseIsDown);
+ if (mouseCtx.down) {
+ $this.#sendEventUpdate(mouseCtx, e);
+ }
+ }
+
+ let deviceDisplayList = document.getElementsByClassName('device-display');
+ if (window.PointerEvent) {
+ for (const deviceDisplay of deviceDisplayList) {
+ deviceDisplay.addEventListener('pointerdown', onStartDrag);
+ deviceDisplay.addEventListener('pointermove', onContinueDrag);
+ deviceDisplay.addEventListener('pointerup', onEndDrag);
+ }
+ } else if (window.TouchEvent) {
+ for (const deviceDisplay of deviceDisplayList) {
+ deviceDisplay.addEventListener('touchstart', onStartDrag);
+ deviceDisplay.addEventListener('touchmove', onContinueDrag);
+ deviceDisplay.addEventListener('touchend', onEndDrag);
+ }
+ } else if (window.MouseEvent) {
+ for (const deviceDisplay of deviceDisplayList) {
+ deviceDisplay.addEventListener('mousedown', onStartDrag);
+ deviceDisplay.addEventListener('mousemove', onContinueDrag);
+ deviceDisplay.addEventListener('mouseup', onEndDrag);
+ }
+ }
+ }
+
+ #sendEventUpdate(ctx, e) {
+ let eventType = e.type.substring(0, 5);
+
+ // The <video> element:
+ const deviceDisplay = e.target;
+
+ // Before the first video frame arrives there is no way to know width and
+ // height of the device's screen, so turn every click into a click at 0x0.
+ // A click at that position is not more dangerous than anywhere else since
+ // the user is clicking blind anyways.
+ const videoWidth = deviceDisplay.videoWidth ? deviceDisplay.videoWidth : 1;
+ const videoHeight =
+ deviceDisplay.videoHeight ? deviceDisplay.videoHeight : 1;
+ const elementWidth =
+ deviceDisplay.offsetWidth ? deviceDisplay.offsetWidth : 1;
+ const elementHeight =
+ deviceDisplay.offsetHeight ? deviceDisplay.offsetHeight : 1;
+
+ // vh*ew > eh*vw? then scale h instead of w
+ const scaleHeight = videoHeight * elementWidth > videoWidth * elementHeight;
+ let elementScaling = 0, videoScaling = 0;
+ if (scaleHeight) {
+ elementScaling = elementHeight;
+ videoScaling = videoHeight;
+ } else {
+ elementScaling = elementWidth;
+ videoScaling = videoWidth;
+ }
+
+ // The screen uses the 'object-fit: cover' property in order to completely
+ // fill the element while maintaining the screen content's aspect ratio.
+ // Therefore:
+ // - If vh*ew > eh*vw, w is scaled so that content width == element width
+ // - Otherwise, h is scaled so that content height == element height
+ const scaleWidth = videoHeight * elementWidth > videoWidth * elementHeight;
+
+ // Convert to coordinates relative to the video by scaling.
+ // (This matches the scaling used by 'object-fit: cover'.)
+ //
+ // This scaling is needed to translate from the in-browser x/y to the
+ // on-device x/y.
+ // - When the device screen has not been resized, this is simple: scale
+ // the coordinates based on the ratio between the input video size and
+ // the in-browser size.
+ // - When the device screen has been resized, this scaling is still needed
+ // even though the in-browser size and device size are identical. This
+ // is due to the way WindowManager handles a resized screen, resized via
+ // `adb shell wm size`:
+ // - The ABS_X and ABS_Y max values of the screen retain their
+ // original values equal to the value set when launching the device
+ // (which equals the video size here).
+ // - The sent ABS_X and ABS_Y values need to be scaled based on the
+ // ratio between the max size (video size) and in-browser size.
+ const scaling =
+ scaleWidth ? videoWidth / elementWidth : videoHeight / elementHeight;
+
+ let xArr = [];
+ let yArr = [];
+ let idArr = [];
+ let slotArr = [];
+
+ if (eventType == 'mouse' || eventType == 'point') {
+ xArr.push(e.offsetX);
+ yArr.push(e.offsetY);
+
+ let thisId = -1;
+ if (eventType == 'point') {
+ thisId = e.pointerId;
+ }
+
+ slotArr.push(0);
+ idArr.push(thisId);
+ } else if (eventType == 'touch') {
+ // touchstart: list of touch points that became active
+ // touchmove: list of touch points that changed
+ // touchend: list of touch points that were removed
+ let changes = e.changedTouches;
+ let rect = e.target.getBoundingClientRect();
+ for (let i = 0; i < changes.length; i++) {
+ xArr.push(changes[i].pageX - rect.left);
+ yArr.push(changes[i].pageY - rect.top);
+ if (ctx.touchIdSlotMap.has(changes[i].identifier)) {
+ let slot = ctx.touchIdSlotMap.get(changes[i].identifier);
+
+ slotArr.push(slot);
+ if (e.type == 'touchstart') {
+ // error
+ console.error('touchstart when already have slot');
+ return;
+ } else if (e.type == 'touchmove') {
+ idArr.push(changes[i].identifier);
+ } else if (e.type == 'touchend') {
+ ctx.touchSlots[slot] = false;
+ ctx.touchIdSlotMap.delete(changes[i].identifier);
+ idArr.push(-1);
+ }
+ } else {
+ if (e.type == 'touchstart') {
+ let slot = -1;
+ for (let j = 0; j < ctx.touchSlots.length; j++) {
+ if (!ctx.touchSlots[j]) {
+ slot = j;
+ break;
+ }
+ }
+ if (slot == -1) {
+ slot = ctx.touchSlots.length;
+ ctx.touchSlots.push(true);
+ }
+ slotArr.push(slot);
+ ctx.touchSlots[slot] = true;
+ ctx.touchIdSlotMap.set(changes[i].identifier, slot);
+ idArr.push(changes[i].identifier);
+ } else if (e.type == 'touchmove') {
+ // error
+ console.error('touchmove when no slot');
+ return;
+ } else if (e.type == 'touchend') {
+ // error
+ console.error('touchend when no slot');
+ return;
+ }
+ }
+ }
+ }
+
+ for (let i = 0; i < xArr.length; i++) {
+ xArr[i] = xArr[i] * scaling;
+ yArr[i] = yArr[i] * scaling;
+
+ // Substract the offset produced by the difference in aspect ratio, if
+ // any.
+ if (scaleWidth) {
+ // Width was scaled, leaving excess content height, so subtract from y.
+ yArr[i] -= (elementHeight * scaling - videoHeight) / 2;
+ } else {
+ // Height was scaled, leaving excess content width, so subtract from x.
+ xArr[i] -= (elementWidth * scaling - videoWidth) / 2;
+ }
+
+ xArr[i] = Math.trunc(xArr[i]);
+ yArr[i] = Math.trunc(yArr[i]);
+ }
+
+ // NOTE: Rotation is handled automatically because the CSS rotation through
+ // transforms also rotates the coordinates of events on the object.
+
+ const display_label = deviceDisplay.id;
+
+ this.#deviceConnection.sendMultiTouch(
+ {idArr, xArr, yArr, down: ctx.down, slotArr, display_label});
+ }
+
+ #updateDisplayVisibility(displayId, powerMode) {
+ const display = document.getElementById('display_' + displayId).parentElement;
+ if (display == null) {
+ console.error('Unknown display id: ' + displayId);
+ return;
+ }
+ powerMode = powerMode.toLowerCase();
+ switch (powerMode) {
+ case 'on':
+ display.style.visibility = 'visible';
+ break;
+ case 'off':
+ display.style.visibility = 'hidden';
+ break;
+ default:
+ console.error('Display ' + displayId + ' has unknown display power mode: ' + powerMode);
+ }
+ }
+
+ #onMicCaptureToggle(enabled) {
+ return this.#deviceConnection.useMic(enabled);
+ }
+
+ #onCameraCaptureToggle(enabled) {
+ return this.#deviceConnection.useCamera(enabled);
+ }
+
+ #getZeroPaddedString(value, desiredLength) {
+ const s = String(value);
+ return '0'.repeat(desiredLength - s.length) + s;
+ }
+
+ #getTimestampString() {
+ const now = new Date();
+ return [
+ now.getFullYear(),
+ this.#getZeroPaddedString(now.getMonth(), 2),
+ this.#getZeroPaddedString(now.getDay(), 2),
+ this.#getZeroPaddedString(now.getHours(), 2),
+ this.#getZeroPaddedString(now.getMinutes(), 2),
+ this.#getZeroPaddedString(now.getSeconds(), 2),
+ ].join('_');
+ }
+
+ #onVideoCaptureToggle(enabled) {
+ const recordToggle = document.getElementById('record-video-control');
+ if (enabled) {
+ let recorders = [];
+
+ const timestamp = this.#getTimestampString();
+
+ let deviceDisplayVideoList =
+ document.getElementsByClassName('device-display-video');
+ for (let i = 0; i < deviceDisplayVideoList.length; i++) {
+ const deviceDisplayVideo = deviceDisplayVideoList[i];
+
+ const recorder = new MediaRecorder(deviceDisplayVideo.captureStream());
+ const recordedData = [];
+
+ recorder.ondataavailable = event => recordedData.push(event.data);
+ recorder.onstop = event => {
+ const recording = new Blob(recordedData, { type: "video/webm" });
+
+ const downloadLink = document.createElement('a');
+ downloadLink.setAttribute('download', timestamp + '_display_' + i + '.webm');
+ downloadLink.setAttribute('href', URL.createObjectURL(recording));
+ downloadLink.click();
+ };
+
+ recorder.start();
+ recorders.push(recorder);
+ }
+ this.#recording['recorders'] = recorders;
+
+ recordToggle.style.backgroundColor = 'red';
+ } else {
+ for (const recorder of this.#recording['recorders']) {
+ recorder.stop();
+ }
+ recordToggle.style.backgroundColor = '';
+ }
+ return Promise.resolve(enabled);
+ }
+
+ #onAudioPlaybackToggle(enabled) {
+ const audioElem = document.getElementById('device-audio');
+ if (enabled) {
+ audioElem.play();
+ } else {
+ audioElem.pause();
+ }
+ }
+
+ #onCustomShellButton(shell_command, e) {
+ // Attempt to init adb again, in case the initial connection failed.
+ // This succeeds immediately if already connected.
+ this.#initializeAdb();
+ if (e.type == 'mousedown') {
+ adbShell(shell_command);
+ }
+ }
+} // DeviceControlApp
+
+window.addEventListener("load", async evt => {
+ try {
+ setupMessages();
+ let connectorModule = await import('./server_connector.js');
+ let deviceConnection = await ConnectDevice(
+ connectorModule.deviceId(), await connectorModule.createConnector());
+ let deviceControlApp = new DeviceControlApp(deviceConnection);
+ deviceControlApp.start();
+ document.getElementById('device-connection').style.display = 'block';
+ } catch(err) {
+ console.error('Unable to connect: ', err);
+ showError(
+ 'No connection to the guest device. ' +
+ 'Please ensure the WebRTC process on the host machine is active.');
+ }
+ document.getElementById('loader').style.display = 'none';
+});
diff --git a/host/frontend/webrtc/client/js/cf_webrtc.js b/host/frontend/webrtc/client/js/cf_webrtc.js
new file mode 100644
index 0000000..5c91383
--- /dev/null
+++ b/host/frontend/webrtc/client/js/cf_webrtc.js
@@ -0,0 +1,496 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+function createDataChannel(pc, label, onMessage) {
+ console.debug('creating data channel: ' + label);
+ let dataChannel = pc.createDataChannel(label);
+ // Return an object with a send function like that of the dataChannel, but
+ // that only actually sends over the data channel once it has connected.
+ return {
+ channelPromise: new Promise((resolve, reject) => {
+ dataChannel.onopen = (event) => {
+ resolve(dataChannel);
+ };
+ dataChannel.onclose = () => {
+ console.debug(
+ 'Data channel=' + label + ' state=' + dataChannel.readyState);
+ };
+ dataChannel.onmessage = onMessage ? onMessage : (msg) => {
+ console.debug('Data channel=' + label + ' data="' + msg.data + '"');
+ };
+ dataChannel.onerror = err => {
+ reject(err);
+ };
+ }),
+ send: function(msg) {
+ this.channelPromise = this.channelPromise.then(channel => {
+ channel.send(msg);
+ return channel;
+ })
+ },
+ };
+}
+
+function awaitDataChannel(pc, label, onMessage) {
+ console.debug('expecting data channel: ' + label);
+ // Return an object with a send function like that of the dataChannel, but
+ // that only actually sends over the data channel once it has connected.
+ return {
+ channelPromise: new Promise((resolve, reject) => {
+ let prev_ondatachannel = pc.ondatachannel;
+ pc.ondatachannel = ev => {
+ let dataChannel = ev.channel;
+ if (dataChannel.label == label) {
+ dataChannel.onopen = (event) => {
+ resolve(dataChannel);
+ };
+ dataChannel.onclose = () => {
+ console.debug(
+ 'Data channel=' + label + ' state=' + dataChannel.readyState);
+ };
+ dataChannel.onmessage = onMessage ? onMessage : (msg) => {
+ console.debug('Data channel=' + label + ' data="' + msg.data + '"');
+ };
+ dataChannel.onerror = err => {
+ reject(err);
+ };
+ } else if (prev_ondatachannel) {
+ prev_ondatachannel(ev);
+ }
+ };
+ }),
+ send: function(msg) {
+ this.channelPromise = this.channelPromise.then(channel => {
+ channel.send(msg);
+ return channel;
+ })
+ },
+ };
+}
+
+class DeviceConnection {
+ #pc;
+ #control;
+ #description;
+
+ #cameraDataChannel;
+ #cameraInputQueue;
+ #controlChannel;
+ #inputChannel;
+ #adbChannel;
+ #bluetoothChannel;
+
+ #streams;
+ #streamPromiseResolvers;
+ #micSenders = [];
+ #cameraSenders = [];
+ #camera_res_x;
+ #camera_res_y;
+
+ #onAdbMessage;
+ #onControlMessage;
+ #onBluetoothMessage;
+
+ constructor(pc, control) {
+ this.#pc = pc;
+ this.#control = control;
+ this.#cameraDataChannel = pc.createDataChannel('camera-data-channel');
+ this.#cameraDataChannel.binaryType = 'arraybuffer';
+ this.#cameraInputQueue = new Array();
+ var self = this;
+ this.#cameraDataChannel.onbufferedamountlow = () => {
+ if (self.#cameraInputQueue.length > 0) {
+ self.sendCameraData(self.#cameraInputQueue.shift());
+ }
+ };
+ this.#inputChannel = createDataChannel(pc, 'input-channel');
+ this.#adbChannel = createDataChannel(pc, 'adb-channel', (msg) => {
+ if (this.#onAdbMessage) {
+ this.#onAdbMessage(msg.data);
+ } else {
+ console.error('Received unexpected ADB message');
+ }
+ });
+ this.#controlChannel = awaitDataChannel(pc, 'device-control', (msg) => {
+ if (this.#onControlMessage) {
+ this.#onControlMessage(msg);
+ } else {
+ console.error('Received unexpected Control message');
+ }
+ });
+ this.#bluetoothChannel =
+ createDataChannel(pc, 'bluetooth-channel', (msg) => {
+ if (this.#onBluetoothMessage) {
+ this.#onBluetoothMessage(msg.data);
+ } else {
+ console.error('Received unexpected Bluetooth message');
+ }
+ });
+ this.#streams = {};
+ this.#streamPromiseResolvers = {};
+
+ pc.addEventListener('track', e => {
+ console.debug('Got remote stream: ', e);
+ for (const stream of e.streams) {
+ this.#streams[stream.id] = stream;
+ if (this.#streamPromiseResolvers[stream.id]) {
+ for (let resolver of this.#streamPromiseResolvers[stream.id]) {
+ resolver();
+ }
+ delete this.#streamPromiseResolvers[stream.id];
+ }
+ }
+ });
+ }
+
+ set description(desc) {
+ this.#description = desc;
+ }
+
+ get description() {
+ return this.#description;
+ }
+
+ get imageCapture() {
+ if (this.#cameraSenders && this.#cameraSenders.length > 0) {
+ let track = this.#cameraSenders[0].track;
+ return new ImageCapture(track);
+ }
+ return undefined;
+ }
+
+ get cameraWidth() {
+ return this.#camera_res_x;
+ }
+
+ get cameraHeight() {
+ return this.#camera_res_y;
+ }
+
+ get cameraEnabled() {
+ return this.#cameraSenders && this.#cameraSenders.length > 0;
+ }
+
+ getStream(stream_id) {
+ return new Promise((resolve, reject) => {
+ if (this.#streams[stream_id]) {
+ resolve(this.#streams[stream_id]);
+ } else {
+ if (!this.#streamPromiseResolvers[stream_id]) {
+ this.#streamPromiseResolvers[stream_id] = [];
+ }
+ this.#streamPromiseResolvers[stream_id].push(resolve);
+ }
+ });
+ }
+
+ #sendJsonInput(evt) {
+ this.#inputChannel.send(JSON.stringify(evt));
+ }
+
+ sendMousePosition({x, y, down, display_label}) {
+ this.#sendJsonInput({
+ type: 'mouse',
+ down: down ? 1 : 0,
+ x,
+ y,
+ display_label,
+ });
+ }
+
+ // TODO (b/124121375): This should probably be an array of pointer events and
+ // have different properties.
+ sendMultiTouch({idArr, xArr, yArr, down, slotArr, display_label}) {
+ this.#sendJsonInput({
+ type: 'multi-touch',
+ id: idArr,
+ x: xArr,
+ y: yArr,
+ down: down ? 1 : 0,
+ slot: slotArr,
+ display_label: display_label,
+ });
+ }
+
+ sendKeyEvent(code, type) {
+ this.#sendJsonInput({type: 'keyboard', keycode: code, event_type: type});
+ }
+
+ disconnect() {
+ this.#pc.close();
+ }
+
+ // Sends binary data directly to the in-device adb daemon (skipping the host)
+ sendAdbMessage(msg) {
+ this.#adbChannel.send(msg);
+ }
+
+ // Provide a callback to receive data from the in-device adb daemon
+ onAdbMessage(cb) {
+ this.#onAdbMessage = cb;
+ }
+
+ // Send control commands to the device
+ sendControlMessage(msg) {
+ this.#controlChannel.send(msg);
+ }
+
+ async #useDevice(in_use, senders_arr, device_opt) {
+ // An empty array means no tracks are currently in use
+ if (senders_arr.length > 0 === !!in_use) {
+ console.warn('Device is already ' + (in_use ? '' : 'not ') + 'in use');
+ return in_use;
+ }
+ let renegotiation_needed = false;
+ if (in_use) {
+ try {
+ let stream = await navigator.mediaDevices.getUserMedia(device_opt);
+ stream.getTracks().forEach(track => {
+ console.info(`Using ${track.kind} device: ${track.label}`);
+ senders_arr.push(this.#pc.addTrack(track));
+ renegotiation_needed = true;
+ });
+ } catch (e) {
+ console.error('Failed to add stream to peer connection: ', e);
+ // Don't return yet, if there were errors some tracks may have been
+ // added so the connection should be renegotiated again.
+ }
+ } else {
+ for (const sender of senders_arr) {
+ console.info(
+ `Removing ${sender.track.kind} device: ${sender.track.label}`);
+ let track = sender.track;
+ track.stop();
+ this.#pc.removeTrack(sender);
+ renegotiation_needed = true;
+ }
+ // Empty the array passed by reference, just assigning [] won't do that.
+ senders_arr.length = 0;
+ }
+ if (renegotiation_needed) {
+ this.#control.renegotiateConnection();
+ }
+ // Return the new state
+ return senders_arr.length > 0;
+ }
+
+ async useMic(in_use) {
+ return this.#useDevice(in_use, this.#micSenders, {audio: true, video: false});
+ }
+
+ async useCamera(in_use) {
+ return this.#useDevice(in_use, this.#micSenders, {audio: false, video: true});
+ }
+
+ sendCameraResolution(stream) {
+ const cameraTracks = stream.getVideoTracks();
+ if (cameraTracks.length > 0) {
+ const settings = cameraTracks[0].getSettings();
+ this.#camera_res_x = settings.width;
+ this.#camera_res_y = settings.height;
+ this.sendControlMessage(JSON.stringify({
+ command: 'camera_settings',
+ width: settings.width,
+ height: settings.height,
+ frame_rate: settings.frameRate,
+ facing: settings.facingMode
+ }));
+ }
+ }
+
+ sendOrQueueCameraData(data) {
+ if (this.#cameraDataChannel.bufferedAmount > 0 ||
+ this.#cameraInputQueue.length > 0) {
+ this.#cameraInputQueue.push(data);
+ } else {
+ this.sendCameraData(data);
+ }
+ }
+
+ sendCameraData(data) {
+ const MAX_SIZE = 65535;
+ const END_MARKER = 'EOF';
+ for (let i = 0; i < data.byteLength; i += MAX_SIZE) {
+ // range is clamped to the valid index range
+ this.#cameraDataChannel.send(data.slice(i, i + MAX_SIZE));
+ }
+ this.#cameraDataChannel.send(END_MARKER);
+ }
+
+ // Provide a callback to receive control-related comms from the device
+ onControlMessage(cb) {
+ this.#onControlMessage = cb;
+ }
+
+ sendBluetoothMessage(msg) {
+ this.#bluetoothChannel.send(msg);
+ }
+
+ onBluetoothMessage(cb) {
+ this.#onBluetoothMessage = cb;
+ }
+
+ // Provide a callback to receive connectionstatechange states.
+ onConnectionStateChange(cb) {
+ this.#pc.addEventListener(
+ 'connectionstatechange', evt => cb(this.#pc.connectionState));
+ }
+}
+
+class Controller {
+ #pc;
+ #serverConnector;
+
+ constructor(serverConnector) {
+ this.#serverConnector = serverConnector;
+ serverConnector.onDeviceMsg(msg => this.#onDeviceMessage(msg));
+ }
+
+ #onDeviceMessage(message) {
+ let type = message.type;
+ switch (type) {
+ case 'offer':
+ this.#onOffer({type: 'offer', sdp: message.sdp});
+ break;
+ case 'answer':
+ this.#onAnswer({type: 'answer', sdp: message.sdp});
+ break;
+ case 'ice-candidate':
+ this.#onIceCandidate(new RTCIceCandidate({
+ sdpMid: message.mid,
+ sdpMLineIndex: message.mLineIndex,
+ candidate: message.candidate
+ }));
+ break;
+ case 'error':
+ console.error('Device responded with error message: ', message.error);
+ break;
+ default:
+ console.error('Unrecognized message type from device: ', type);
+ }
+ }
+
+ async #sendClientDescription(desc) {
+ console.debug('sendClientDescription');
+ return this.#serverConnector.sendToDevice({type: 'answer', sdp: desc.sdp});
+ }
+
+ async #sendIceCandidate(candidate) {
+ console.debug('sendIceCandidate');
+ return this.#serverConnector.sendToDevice({type: 'ice-candidate', candidate});
+ }
+
+ async #onOffer(desc) {
+ console.debug('Remote description (offer): ', desc);
+ try {
+ await this.#pc.setRemoteDescription(desc);
+ let answer = await this.#pc.createAnswer();
+ console.debug('Answer: ', answer);
+ await this.#pc.setLocalDescription(answer);
+ await this.#sendClientDescription(answer);
+ } catch (e) {
+ console.error('Error processing remote description (offer)', e)
+ throw e;
+ }
+ }
+
+ async #onAnswer(answer) {
+ console.debug('Remote description (answer): ', answer);
+ try {
+ await this.#pc.setRemoteDescription(answer);
+ } catch (e) {
+ console.error('Error processing remote description (answer)', e)
+ throw e;
+ }
+ }
+
+ #onIceCandidate(iceCandidate) {
+ console.debug(`Remote ICE Candidate: `, iceCandidate);
+ this.#pc.addIceCandidate(iceCandidate);
+ }
+
+ ConnectDevice(pc) {
+ this.#pc = pc;
+ console.debug('ConnectDevice');
+ // ICE candidates will be generated when we add the offer. Adding it here
+ // instead of in _onOffer because this function is called once per peer
+ // connection, while _onOffer may be called more than once due to
+ // renegotiations.
+ this.#pc.addEventListener('icecandidate', evt => {
+ if (evt.candidate) this.#sendIceCandidate(evt.candidate);
+ });
+ this.#serverConnector.sendToDevice({type: 'request-offer'});
+ }
+
+ async renegotiateConnection() {
+ console.debug('Re-negotiating connection');
+ let offer = await this.#pc.createOffer();
+ console.debug('Local description (offer): ', offer);
+ await this.#pc.setLocalDescription(offer);
+ this.#serverConnector.sendToDevice({type: 'offer', sdp: offer.sdp});
+ }
+}
+
+function createPeerConnection(infra_config) {
+ let pc_config = {iceServers: []};
+ for (const stun of infra_config.ice_servers) {
+ pc_config.iceServers.push({urls: 'stun:' + stun});
+ }
+ let pc = new RTCPeerConnection(pc_config);
+
+ pc.addEventListener('icecandidate', evt => {
+ console.debug('Local ICE Candidate: ', evt.candidate);
+ });
+ pc.addEventListener('iceconnectionstatechange', evt => {
+ console.debug(`ICE State Change: ${pc.iceConnectionState}`);
+ });
+ pc.addEventListener(
+ 'connectionstatechange',
+ evt => console.debug(
+ `WebRTC Connection State Change: ${pc.connectionState}`));
+ return pc;
+}
+
+export async function Connect(deviceId, serverConnector) {
+ let requestRet = await serverConnector.requestDevice(deviceId);
+ let deviceInfo = requestRet.deviceInfo;
+ let infraConfig = requestRet.infraConfig;
+ console.debug('Device available:');
+ console.debug(deviceInfo);
+ let pc_config = {iceServers: []};
+ if (infraConfig.ice_servers && infraConfig.ice_servers.length > 0) {
+ for (const server of infraConfig.ice_servers) {
+ pc_config.iceServers.push(server);
+ }
+ }
+ let pc = createPeerConnection(infraConfig);
+
+ let control = new Controller(serverConnector);
+ let deviceConnection = new DeviceConnection(pc, control);
+ deviceConnection.description = deviceInfo;
+
+ return new Promise((resolve, reject) => {
+ pc.addEventListener('connectionstatechange', evt => {
+ let state = pc.connectionState;
+ if (state == 'connected') {
+ resolve(deviceConnection);
+ } else if (state == 'failed') {
+ reject(evt);
+ }
+ });
+ control.ConnectDevice(pc);
+ });
+}
diff --git a/host/frontend/webrtc/client/js/controls.js b/host/frontend/webrtc/client/js/controls.js
new file mode 100644
index 0000000..c9aaac4
--- /dev/null
+++ b/host/frontend/webrtc/client/js/controls.js
@@ -0,0 +1,342 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+// Creates a "toggle control", which is a stylized checkbox with an icon. The
+// onToggleCb callback is called every time the control changes state with the
+// new toggle position (true for ON) and is expected to return a promise of the
+// new toggle position which can resolve to the opposite position of the one
+// received if there was error.
+function createToggleControl(elm, iconName, onToggleCb, initialState = false) {
+ let icon = document.createElement('span');
+ icon.classList.add('toggle-control-icon');
+ icon.classList.add('material-icons-outlined');
+ if (iconName) {
+ icon.appendChild(document.createTextNode(iconName));
+ }
+ elm.appendChild(icon);
+ let toggle = document.createElement('label');
+ toggle.classList.add('toggle-control-switch');
+ let input = document.createElement('input');
+ input.type = 'checkbox';
+ input.checked = !!initialState;
+ input.onchange = e => {
+ let nextPr = onToggleCb(e.target.checked);
+ if (nextPr && 'then' in nextPr) {
+ nextPr.then(checked => {
+ e.target.checked = !!checked;
+ });
+ }
+ };
+ toggle.appendChild(input);
+ let slider = document.createElement('span');
+ slider.classList.add('toggle-control-slider');
+ toggle.appendChild(slider);
+ elm.classList.add('toggle-control');
+ elm.appendChild(toggle);
+ return {
+ // Sets the state of the toggle control. This only affects the
+ // visible state of the control in the UI, it doesn't affect the
+ // state of the underlying resources. It's most useful to make
+ // changes of said resources visible to the user.
+ Set: checked => input.checked = !!checked,
+ };
+}
+
+function createButtonListener(button_id_class, func,
+ deviceConnection, listener) {
+ let buttons = [];
+ let ele = document.getElementById(button_id_class);
+ if (ele != null) {
+ buttons.push(ele);
+ } else {
+ buttons = document.getElementsByClassName(button_id_class);
+ }
+ for (var button of buttons) {
+ if (func != null) {
+ button.onclick = func;
+ }
+ button.addEventListener('mousedown', listener);
+ }
+}
+
+function createInputListener(input_id, func, listener) {
+ input = document.getElementById(input_id);
+ if (func != null) {
+ input.oninput = func;
+ }
+ input.addEventListener('input', listener);
+}
+
+function validateMacAddress(val) {
+ var regex = /^([0-9A-Fa-f]{2}[:-]){5}([0-9A-Fa-f]{2})$/;
+ return (regex.test(val));
+}
+
+function validateMacWrapper() {
+ let type = document.getElementById('bluetooth-wizard-type').value;
+ let button = document.getElementById("bluetooth-wizard-device");
+ let macField = document.getElementById('bluetooth-wizard-mac');
+ if (this.id == 'bluetooth-wizard-type') {
+ if (type == "remote_loopback") {
+ button.disabled = false;
+ macField.setCustomValidity('');
+ macField.disabled = true;
+ macField.required = false;
+ macField.placeholder = 'N/A';
+ macField.value = '';
+ return;
+ }
+ }
+ macField.disabled = false;
+ macField.required = true;
+ macField.placeholder = 'Device MAC';
+ if (validateMacAddress($(macField).val())) {
+ button.disabled = false;
+ macField.setCustomValidity('');
+ } else {
+ button.disabled = true;
+ macField.setCustomValidity('MAC address invalid');
+ }
+}
+
+$('[validate-mac]').bind('input', validateMacWrapper);
+$('[validate-mac]').bind('select', validateMacWrapper);
+
+function parseDevice(device) {
+ let id, name, mac;
+ var regex = /([0-9]+):([^@ ]*)(@(([0-9A-Fa-f]{2}[:-]){5}([0-9A-Fa-f]{2})))?/;
+ if (regex.test(device)) {
+ let regexMatches = device.match(regex);
+ id = regexMatches[1];
+ name = regexMatches[2];
+ mac = regexMatches[4];
+ }
+ if (mac === undefined) {
+ mac = "";
+ }
+ return [id, name, mac];
+}
+
+function btUpdateAdded(devices) {
+ let deviceArr = devices.split('\r\n');
+ let [id, name, mac] = parseDevice(deviceArr[0]);
+ if (name) {
+ let div = document.getElementById('bluetooth-wizard-confirm').getElementsByClassName('bluetooth-text')[1];
+ div.innerHTML = "";
+ div.innerHTML += "<p>Name: <b>" + id + "</b></p>";
+ div.innerHTML += "<p>Type: <b>" + name + "</b></p>";
+ div.innerHTML += "<p>MAC Addr: <b>" + mac + "</b></p>";
+ return true;
+ }
+ return false;
+}
+
+function parsePhy(phy) {
+ let id = phy.substring(0, phy.indexOf(":"));
+ phy = phy.substring(phy.indexOf(":") + 1);
+ let name = phy.substring(0, phy.indexOf(":"));
+ let devices = phy.substring(phy.indexOf(":") + 1);
+ return [id, name, devices];
+}
+
+function btParsePhys(phys) {
+ if (phys.indexOf("Phys:") < 0) {
+ return null;
+ }
+ let phyDict = {};
+ phys = phys.split('Phys:')[1];
+ let phyArr = phys.split('\r\n');
+ for (var phy of phyArr.slice(1)) {
+ phy = phy.trim();
+ if (phy.length == 0 || phy.indexOf("deleted") >= 0) {
+ continue;
+ }
+ let [id, name, devices] = parsePhy(phy);
+ phyDict[name] = id;
+ }
+ return phyDict;
+}
+
+function btUpdateDeviceList(devices) {
+ let deviceArr = devices.split('\r\n');
+ if (deviceArr[0].indexOf("Devices:") >= 0) {
+ let div = document.getElementById('bluetooth-list').getElementsByClassName('bluetooth-text')[0];
+ div.innerHTML = "";
+ let count = 0;
+ for (var device of deviceArr.slice(1)) {
+ if (device.indexOf("Phys:") >= 0) {
+ break;
+ }
+ count++;
+ if (device.indexOf("deleted") >= 0) {
+ continue;
+ }
+ let [id, name, mac] = parseDevice(device);
+ let innerDiv = '<div><button title="Delete" data-device-id="'
+ innerDiv += id;
+ innerDiv += '" class="bluetooth-list-trash material-icons">delete</button>';
+ innerDiv += name;
+ if (mac) {
+ innerDiv += " | "
+ innerDiv += mac;
+ }
+ innerDiv += '</div>';
+ div.innerHTML += innerDiv;
+ }
+ return count;
+ }
+ return -1;
+}
+
+function createControlPanelButton(
+ command, title, icon_name, listener,
+ parent_id = 'control-panel-default-buttons') {
+ let button = document.createElement('button');
+ document.getElementById(parent_id).appendChild(button);
+ button.title = title;
+ button.dataset.command = command;
+ button.disabled = true;
+ // Capture mousedown/up/out commands instead of click to enable
+ // hold detection. mouseout is used to catch if the user moves the
+ // mouse outside the button while holding down.
+ button.addEventListener('mousedown', listener);
+ button.addEventListener('mouseup', listener);
+ button.addEventListener('mouseout', listener);
+ // Set the button image using Material Design icons.
+ // See http://google.github.io/material-design-icons
+ // and https://material.io/resources/icons
+ button.classList.add('material-icons');
+ button.innerHTML = icon_name;
+ return button;
+}
+
+function positionModal(button_id, modal_id) {
+ const modalButton = document.getElementById(button_id);
+ const modalDiv = document.getElementById(modal_id);
+
+ // Position the modal to the right of the show modal button.
+ modalDiv.style.top = modalButton.offsetTop;
+ modalDiv.style.left = modalButton.offsetWidth + 30;
+}
+
+function createModalButton(button_id, modal_id, close_id, hide_id) {
+ const modalButton = document.getElementById(button_id);
+ const modalDiv = document.getElementById(modal_id);
+ const modalHeader = modalDiv.querySelector('.modal-header');
+ const modalClose = document.getElementById(close_id);
+ const modalDivHide = document.getElementById(hide_id);
+
+ positionModal(button_id, modal_id);
+
+ function showHideModal(show) {
+ if (show) {
+ modalButton.classList.add('modal-button-opened')
+ modalDiv.style.display = 'block';
+ } else {
+ modalButton.classList.remove('modal-button-opened')
+ modalDiv.style.display = 'none';
+ }
+ if (modalDivHide != null) {
+ modalDivHide.style.display = 'none';
+ }
+ }
+ // Allow the show modal button to toggle the modal,
+ modalButton.addEventListener(
+ 'click', evt => showHideModal(modalDiv.style.display != 'block'));
+ // but the close button always closes.
+ modalClose.addEventListener('click', evt => showHideModal(false));
+
+ // Allow the modal to be dragged by the header.
+ let modalOffsets = {
+ midDrag: false,
+ mouseDownOffsetX: null,
+ mouseDownOffsetY: null,
+ };
+ modalHeader.addEventListener('mousedown', evt => {
+ modalOffsets.midDrag = true;
+ // Store the offset of the mouse location from the
+ // modal's current location.
+ modalOffsets.mouseDownOffsetX = parseInt(modalDiv.style.left) - evt.clientX;
+ modalOffsets.mouseDownOffsetY = parseInt(modalDiv.style.top) - evt.clientY;
+ });
+ modalHeader.addEventListener('mousemove', evt => {
+ let offsets = modalOffsets;
+ if (offsets.midDrag) {
+ // Move the modal to the mouse location plus the
+ // offset calculated on the initial mouse-down.
+ modalDiv.style.left = evt.clientX + offsets.mouseDownOffsetX;
+ modalDiv.style.top = evt.clientY + offsets.mouseDownOffsetY;
+ }
+ });
+ document.addEventListener('mouseup', evt => {
+ modalOffsets.midDrag = false;
+ });
+}
+
+function cmdConsole(consoleViewName, consoleInputName) {
+ let consoleView = document.getElementById(consoleViewName);
+
+ let addString =
+ function(str) {
+ consoleView.value += str;
+ consoleView.scrollTop = consoleView.scrollHeight;
+ }
+
+ let addLine =
+ function(line) {
+ addString(line + '\r\n');
+ }
+
+ let commandCallbacks = [];
+
+ let addCommandListener =
+ function(f) {
+ commandCallbacks.push(f);
+ }
+
+ let onCommand =
+ function(cmd) {
+ cmd = cmd.trim();
+
+ if (cmd.length == 0) return;
+
+ commandCallbacks.forEach(f => {
+ f(cmd);
+ })
+ }
+
+ addCommandListener(cmd => addLine('>> ' + cmd));
+
+ let consoleInput = document.getElementById(consoleInputName);
+
+ consoleInput.addEventListener('keydown', e => {
+ if ((e.key && e.key == 'Enter') || e.keyCode == 13) {
+ let command = e.target.value;
+
+ e.target.value = '';
+
+ onCommand(command);
+ }
+ });
+
+ return {
+ consoleView: consoleView,
+ consoleInput: consoleInput,
+ addLine: addLine,
+ addString: addString,
+ addCommandListener: addCommandListener,
+ };
+}
diff --git a/host/frontend/webrtc_operator/assets/js/rootcanal.js b/host/frontend/webrtc/client/js/rootcanal.js
similarity index 84%
rename from host/frontend/webrtc_operator/assets/js/rootcanal.js
rename to host/frontend/webrtc/client/js/rootcanal.js
index 4443bcd..e3783c7 100644
--- a/host/frontend/webrtc_operator/assets/js/rootcanal.js
+++ b/host/frontend/webrtc/client/js/rootcanal.js
@@ -19,12 +19,12 @@
function rootCanalCalculateMessageSize(name, args) {
let result = 0;
- result += 1 + name.length; // length of name + it's data
- result += 1; // count of args
+ result += 1 + name.length; // length of name + it's data
+ result += 1; // count of args
- for(let i = 0; i < args.length; i++) {
- result += 1; // length of args[i]
- result += args[i].length; // data of args[i]
+ for (let i = 0; i < args.length; i++) {
+ result += 1; // length of args[i]
+ result += args[i].length; // data of args[i]
}
return result;
@@ -59,7 +59,7 @@
pos = rootCanalAddString(array, pos, command);
pos = rootCanalAddU8(array, pos, args.length);
- for(let i = 0; i < args.length; i++) {
+ for (let i = 0; i < args.length; i++) {
pos = rootCanalAddString(array, pos, args[i]);
}
@@ -71,4 +71,4 @@
let message = array.slice(1);
return utf8Decoder.decode(message);
-}
\ No newline at end of file
+}
diff --git a/host/frontend/webrtc/client/style.css b/host/frontend/webrtc/client/style.css
new file mode 100644
index 0000000..93aaa05
--- /dev/null
+++ b/host/frontend/webrtc/client/style.css
@@ -0,0 +1,303 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+body {
+ background-color:black;
+ margin: 0;
+ touch-action: none;
+ overscroll-behavior: none;
+}
+
+#device-connection {
+ display: none;
+ max-height: 100vh;
+}
+
+@keyframes spin {
+ 0% { transform: rotate(0deg); }
+ 100% { transform: rotate(360deg); }
+}
+
+#loader {
+ border-left: 12px solid #4285F4;
+ border-top: 12px solid #34A853;
+ border-right: 12px solid #FBBC05;
+ border-bottom: 12px solid #EA4335;
+ border-radius: 50%;
+ width: 70px;
+ height: 70px;
+ animation: spin 1.2s linear infinite;
+ margin: 100px;
+}
+
+/* Top header row. */
+
+#header {
+ height: 64px;
+ /* Items inside this use a row Flexbox.*/
+ display: flex;
+ align-items: center;
+}
+
+#camera-control {
+ display: none !important;
+}
+#record-video-control {
+ display: none !important;
+}
+
+#app-controls {
+ margin-left: 10px;
+}
+#app-controls > div {
+ display: inline-block;
+ position: relative;
+ margin-right: 6px;
+}
+#device-audio {
+ height: 44px;
+}
+
+#error-message-div {
+ flex-grow: 1;
+}
+#error-message {
+ color: white;
+ font-family: 'Open Sans', sans-serif;
+ padding: 10px;
+ margin: 10px;
+ border-radius: 10px;
+}
+#error-message .close-btn {
+ float: right;
+ cursor: pointer;
+}
+#error-message.hidden {
+ display: none;
+}
+#error-message.warning {
+ /* dark red */
+ background-color: #927836;
+}
+#error-message.error {
+ /* dark red */
+ background-color: #900000;
+}
+#status-div {
+ flex-grow: 1;
+}
+#status-message {
+ color: white;
+ font-family: 'Open Sans', sans-serif;
+ padding: 10px;
+ margin: 10px;
+}
+#status-message.connecting {
+ /* dark yellow */
+ background-color: #927836;
+}
+#status-message.error {
+ /* dark red */
+ background-color: #900000;
+}
+#status-message.connected {
+ /* dark green */
+ background-color: #007000;
+}
+
+/* Control panel buttons and device screen(s). */
+
+#controls-and-displays {
+ height: calc(100% - 84px);
+
+ /* Items inside this use a row Flexbox.*/
+ display: flex;
+}
+
+#controls-and-displays > div {
+ margin-left: 5px;
+ margin-right: 5px;
+}
+
+.modal {
+ /* Start out hidden, and use absolute positioning. */
+ display: none;
+ position: absolute;
+
+ border-radius: 10px;
+ padding: 20px;
+ padding-top: 1px;
+
+ background-color: #5f6368ea; /* Semi-transparent Google grey 500 */
+ color: white;
+ font-family: 'Open Sans', sans-serif;
+}
+.modal-header {
+ cursor: move;
+ /* Items inside this use a row Flexbox.*/
+ display: flex;
+ justify-content: space-between;
+}
+.modal-close {
+ color: white;
+ border: none;
+ outline: none;
+ background-color: transparent;
+}
+.modal-button, .modal-button-highlight {
+ background: #e8eaed; /* Google grey 200 */
+ border-radius: 10px;
+ box-shadow: 1px 1px #444444;
+ padding: 10px 20px;
+ color: #000000;
+ display: inline-block;
+ font: normal bold 14px/1 "Open Sans", sans-serif;
+ text-align: center;
+}
+#bluetooth-wizard-mac:valid {
+ border: 2px solid black;
+}
+#bluetooth-wizard-mac:invalid {
+ border: 2px solid red;
+}
+#bluetooth-wizard-mac:invalid + span::before {
+ font-weight: bold;
+ content: 'X';
+ color: red;
+}
+#bluetooth-wizard-mac:valid + span::before {
+ font-weight: bold;
+ content: 'OK';
+ color: green;
+}
+.modal-button {
+ background: #e8eaed; /* Google grey 200 */
+}
+.modal-button-highlight {
+ background: #f4cccc;
+}
+#device-details-modal span {
+ white-space: pre;
+}
+#bluetooth-console-input {
+ width: 100%;
+}
+#bluetooth-console-cmd-label {
+ color: white;
+}
+.bluetooth-text, .bluetooth-text-bold, .bluetooth-text-field input {
+ font: normal 18px/1 "Open Sans", sans-serif;
+}
+.bluetooth-text, .bluetooth-text-bold {
+ color: white;
+}
+.bluetooth-text-bold {
+ font: bold;
+}
+.bluetooth-button {
+ text-align: center;
+}
+.bluetooth-drop-down select {
+ font: normal 18px/1 "Open Sans", sans-serif;
+ color: black;
+ width: 500px;
+ margin: 5px;
+ rows: 10;
+ columns: 60;
+}
+.bluetooth-text-field input {
+ color: black;
+ width: 500px;
+ margin: 5px;
+ rows: 10;
+ columns: 60;
+}
+.bluetooth-list-trash {
+ background: #00000000;
+ border: 0px;
+ color: #ffffff;
+}
+
+.control-panel-column {
+ width: 50px;
+ /* Items inside this use a column Flexbox.*/
+ display: flex;
+ flex-direction: column;
+}
+#control-panel-custom-buttons {
+ display: none;
+ /* Give the custom buttons column a blue background. */
+ background-color: #1c4587ff;
+ height: fit-content;
+ border-radius: 10px;
+}
+
+.control-panel-column button {
+ margin: 0px 0px 5px 0px;
+ height: 50px;
+ font-size: 32px;
+
+ color: #e8eaed; /* Google grey 200 */
+ border: none;
+ outline: none;
+ background-color: transparent;
+}
+.control-panel-column button:disabled {
+ color: #9aa0a6; /* Google grey 500 */
+}
+.control-panel-column button.modal-button-opened {
+ border-radius: 10px;
+ background-color: #5f6368; /* Google grey 700 */
+}
+
+#device-displays {
+ /* Take up the remaining width of the window.*/
+ flex-grow: 1;
+ /* Don't grow taller than the window.*/
+ max-height: 100vh;
+ /* Allows child elements to be positioned relative to this element. */
+ position: relative;
+}
+
+/*
+ * Container <div> used to wrap each display's <video> element which is used for
+ * maintaining each display's width and height while the display is potentially
+ * rotating.
+ */
+.device-display {
+ /* Prevents #device-displays from using this element when computing flex size. */
+ position: absolute;
+}
+
+/* Container <div> to show info about the individual display. */
+.device-display-info {
+ color: white;
+ /* dark green */
+ background-color: #007000;
+ font-family: 'Open Sans', sans-serif;
+ text-indent: 0px;
+ border-radius: 10px;
+ padding: 10px;
+ margin-bottom: 10px;
+}
+
+/* The actual <video> element for each display. */
+.device-display-video {
+ position: absolute;
+ left: 0px;
+ touch-action: none;
+ object-fit: cover;
+}
diff --git a/host/frontend/webrtc/client_server.cpp b/host/frontend/webrtc/client_server.cpp
new file mode 100644
index 0000000..cfd4bb7
--- /dev/null
+++ b/host/frontend/webrtc/client_server.cpp
@@ -0,0 +1,101 @@
+//
+// Copyright (C) 2021 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "host/frontend/webrtc/client_server.h"
+#include <android-base/logging.h>
+
+namespace cuttlefish {
+struct ClientFilesServer::Config {
+ Config(const std::string& dir)
+ : dir_(dir),
+ mount_({
+ .mount_next = nullptr, /* linked-list "next" */
+ .mountpoint = "/", /* mountpoint URL */
+ .origin = dir_.c_str(), /* serve from dir */
+ .def = "client.html", /* default filename */
+ .protocol = nullptr,
+ .cgienv = nullptr,
+ .extra_mimetypes = nullptr,
+ .interpret = nullptr,
+ .cgi_timeout = 0,
+ .cache_max_age = 0,
+ .auth_mask = 0,
+ .cache_reusable = 0,
+ .cache_revalidate = 0,
+ .cache_intermediaries = 0,
+ .origin_protocol = LWSMPRO_FILE, /* files in a dir */
+ .mountpoint_len = 1, /* char count */
+ .basic_auth_login_file = nullptr,
+ }) {
+ memset(&info_, 0, sizeof info_);
+ info_.port = 0; // let the kernel select an available port
+ info_.iface = "127.0.0.1"; // listen only on localhost
+ info_.mounts = &mount_;
+ }
+
+ std::string dir_;
+ lws_http_mount mount_;
+ lws_context_creation_info info_;
+};
+
+ClientFilesServer::ClientFilesServer(std::unique_ptr<Config> config,
+ lws_context* context)
+ : config_(std::move(config)),
+ context_(context),
+ running_(true),
+ server_thread_([this]() { Serve(); }) {}
+
+ClientFilesServer::~ClientFilesServer() {
+ if (running_) {
+ running_ = false;
+ server_thread_.join();
+ }
+ if (context_) {
+ // Release the port and other resources
+ lws_context_destroy(context_);
+ }
+}
+
+std::unique_ptr<ClientFilesServer> ClientFilesServer::New(
+ const std::string& dir) {
+ std::unique_ptr<Config> conf(new Config(dir));
+ if (!conf) {
+ return nullptr;
+ }
+
+ auto ctx = lws_create_context(&conf->info_);
+ if (!ctx) {
+ LOG(ERROR) << "Failed to create lws context";
+ return nullptr;
+ }
+ return std::unique_ptr<ClientFilesServer>(
+ new ClientFilesServer(std::move(conf), ctx));
+}
+
+int ClientFilesServer::port() const {
+ // Get the port for the first (and only) vhost.
+ return lws_get_vhost_listen_port(lws_get_vhost_by_name(context_, "default"));
+}
+
+void ClientFilesServer::Serve() {
+ while (running_) {
+ if (lws_service(context_, 0) < 0) {
+ LOG(ERROR) << "Error serving client files";
+ return;
+ }
+ }
+}
+} // namespace cuttlefish
+
diff --git a/host/frontend/webrtc/client_server.h b/host/frontend/webrtc/client_server.h
new file mode 100644
index 0000000..8d6376e
--- /dev/null
+++ b/host/frontend/webrtc/client_server.h
@@ -0,0 +1,47 @@
+//
+// Copyright (C) 2021 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#pragma once
+
+#include <atomic>
+#include <memory>
+#include <string>
+#include <thread>
+
+#include <libwebsockets.h>
+
+namespace cuttlefish {
+// Utility class to serve the client files in a thread
+class ClientFilesServer {
+ public:
+ ~ClientFilesServer();
+
+ static std::unique_ptr<ClientFilesServer> New(const std::string& dir);
+
+ int port() const;
+
+ private:
+ struct Config;
+
+ ClientFilesServer(std::unique_ptr<Config> config, lws_context* context);
+
+ void Serve();
+
+ std::unique_ptr<Config> config_;
+ lws_context* context_;
+ std::atomic<bool> running_;
+ std::thread server_thread_;
+};
+} // namespace cuttlefish
diff --git a/host/frontend/webrtc/connection_observer.cpp b/host/frontend/webrtc/connection_observer.cpp
index 0975faa..ec632b6 100644
--- a/host/frontend/webrtc/connection_observer.cpp
+++ b/host/frontend/webrtc/connection_observer.cpp
@@ -96,26 +96,25 @@
* i.e. when it is not in the confirmation UI mode (or TEE),
* the control flow will fall back to this ConnectionObserverForAndroid
*/
-class ConnectionObserverForAndroid
+class ConnectionObserverImpl
: public cuttlefish::webrtc_streaming::ConnectionObserver {
public:
- ConnectionObserverForAndroid(
+ ConnectionObserverImpl(
cuttlefish::InputSockets &input_sockets,
cuttlefish::KernelLogEventsHandler *kernel_log_events_handler,
std::map<std::string, cuttlefish::SharedFD>
commands_to_custom_action_servers,
std::weak_ptr<DisplayHandler> display_handler,
- CameraController *camera_controller)
+ CameraController *camera_controller,
+ cuttlefish::confui::HostVirtualInput &confui_input)
: input_sockets_(input_sockets),
kernel_log_events_handler_(kernel_log_events_handler),
commands_to_custom_action_servers_(commands_to_custom_action_servers),
weak_display_handler_(display_handler),
- camera_controller_(camera_controller) {}
- virtual ~ConnectionObserverForAndroid() {
+ camera_controller_(camera_controller),
+ confui_input_(confui_input) {}
+ virtual ~ConnectionObserverImpl() {
auto display_handler = weak_display_handler_.lock();
- if (display_handler) {
- display_handler->DecClientCount();
- }
if (kernel_log_subscription_id_ != -1) {
kernel_log_events_handler_->Unsubscribe(kernel_log_subscription_id_);
}
@@ -125,7 +124,6 @@
/*ctrl_msg_sender*/) override {
auto display_handler = weak_display_handler_.lock();
if (display_handler) {
- display_handler->IncClientCount();
std::thread th([this]() {
// The encoder in libwebrtc won't drop 5 consecutive frames due to frame
// size, so we make sure at least 5 frames are sent every time a client
@@ -142,69 +140,89 @@
});
th.detach();
}
+ }
+
+ void OnTouchEvent(const std::string &display_label, int x, int y,
+ bool down) override {
+ if (confui_input_.IsConfUiActive()) {
+ ConfUiLog(DEBUG) << "delivering a touch event in confirmation UI mode";
+ confui_input_.TouchEvent(x, y, down);
+ return;
+ }
+ auto buffer = GetEventBuffer();
+ if (!buffer) {
+ LOG(ERROR) << "Failed to allocate event buffer";
+ return;
+ }
+ buffer->AddEvent(EV_ABS, ABS_X, x);
+ buffer->AddEvent(EV_ABS, ABS_Y, y);
+ buffer->AddEvent(EV_KEY, BTN_TOUCH, down);
+ buffer->AddEvent(EV_SYN, SYN_REPORT, 0);
+ cuttlefish::WriteAll(input_sockets_.GetTouchClientByLabel(display_label),
+ reinterpret_cast<const char *>(buffer->data()),
+ buffer->size());
+ }
+
+ void OnMultiTouchEvent(const std::string &display_label, Json::Value id,
+ Json::Value slot, Json::Value x, Json::Value y,
+ bool down, int size) {
+ auto buffer = GetEventBuffer();
+ if (!buffer) {
+ LOG(ERROR) << "Failed to allocate event buffer";
+ return;
}
- void OnTouchEvent(const std::string &display_label, int x, int y,
- bool down) override {
- auto buffer = GetEventBuffer();
- if (!buffer) {
- LOG(ERROR) << "Failed to allocate event buffer";
- return;
- }
- buffer->AddEvent(EV_ABS, ABS_X, x);
- buffer->AddEvent(EV_ABS, ABS_Y, y);
- buffer->AddEvent(EV_KEY, BTN_TOUCH, down);
- buffer->AddEvent(EV_SYN, SYN_REPORT, 0);
- cuttlefish::WriteAll(input_sockets_.GetTouchClientByLabel(display_label),
- reinterpret_cast<const char *>(buffer->data()),
- buffer->size());
- }
+ for (int i = 0; i < size; i++) {
+ auto this_slot = slot[i].asInt();
+ auto this_id = id[i].asInt();
+ auto this_x = x[i].asInt();
+ auto this_y = y[i].asInt();
- void OnMultiTouchEvent(const std::string &display_label, Json::Value id,
- Json::Value slot, Json::Value x, Json::Value y,
- bool down, int size) override {
- auto buffer = GetEventBuffer();
- if (!buffer) {
- LOG(ERROR) << "Failed to allocate event buffer";
- return;
- }
-
- for (int i = 0; i < size; i++) {
- auto this_slot = slot[i].asInt();
- auto this_id = id[i].asInt();
- auto this_x = x[i].asInt();
- auto this_y = y[i].asInt();
- buffer->AddEvent(EV_ABS, ABS_MT_SLOT, this_slot);
+ if (confui_input_.IsConfUiActive()) {
if (down) {
- bool is_new = active_touch_slots_.insert(this_slot).second;
- if (is_new) {
- buffer->AddEvent(EV_ABS, ABS_MT_TRACKING_ID, this_id);
- if (active_touch_slots_.size() == 1) {
- buffer->AddEvent(EV_KEY, BTN_TOUCH, 1);
- }
- }
- buffer->AddEvent(EV_ABS, ABS_MT_POSITION_X, this_x);
- buffer->AddEvent(EV_ABS, ABS_MT_POSITION_Y, this_y);
- // send ABS_X and ABS_Y for single-touch compatibility
- buffer->AddEvent(EV_ABS, ABS_X, this_x);
- buffer->AddEvent(EV_ABS, ABS_Y, this_y);
- } else {
- // released touch
+ ConfUiLog(DEBUG) << "Delivering event (" << x << ", " << y
+ << ") to conf ui";
+ }
+ confui_input_.TouchEvent(this_x, this_y, down);
+ continue;
+ }
+
+ buffer->AddEvent(EV_ABS, ABS_MT_SLOT, this_slot);
+ if (down) {
+ bool is_new = active_touch_slots_.insert(this_slot).second;
+ if (is_new) {
buffer->AddEvent(EV_ABS, ABS_MT_TRACKING_ID, this_id);
- active_touch_slots_.erase(this_slot);
- if (active_touch_slots_.empty()) {
- buffer->AddEvent(EV_KEY, BTN_TOUCH, 0);
+ if (active_touch_slots_.size() == 1) {
+ buffer->AddEvent(EV_KEY, BTN_TOUCH, 1);
}
}
+ buffer->AddEvent(EV_ABS, ABS_MT_POSITION_X, this_x);
+ buffer->AddEvent(EV_ABS, ABS_MT_POSITION_Y, this_y);
+ // send ABS_X and ABS_Y for single-touch compatibility
+ buffer->AddEvent(EV_ABS, ABS_X, this_x);
+ buffer->AddEvent(EV_ABS, ABS_Y, this_y);
+ } else {
+ // released touch
+ buffer->AddEvent(EV_ABS, ABS_MT_TRACKING_ID, this_id);
+ active_touch_slots_.erase(this_slot);
+ if (active_touch_slots_.empty()) {
+ buffer->AddEvent(EV_KEY, BTN_TOUCH, 0);
+ }
}
-
- buffer->AddEvent(EV_SYN, SYN_REPORT, 0);
- cuttlefish::WriteAll(input_sockets_.GetTouchClientByLabel(display_label),
- reinterpret_cast<const char *>(buffer->data()),
- buffer->size());
}
+ buffer->AddEvent(EV_SYN, SYN_REPORT, 0);
+ cuttlefish::WriteAll(input_sockets_.GetTouchClientByLabel(display_label),
+ reinterpret_cast<const char *>(buffer->data()),
+ buffer->size());
+ }
+
void OnKeyboardEvent(uint16_t code, bool down) override {
+ if (confui_input_.IsConfUiActive()) {
+ ConfUiLog(DEBUG) << "keyboard event ignored in confirmation UI mode";
+ return;
+ }
+
auto buffer = GetEventBuffer();
if (!buffer) {
LOG(ERROR) << "Failed to allocate event buffer";
@@ -303,8 +321,6 @@
OnKeyboardEvent(KEY_HOMEPAGE, button_state == "down");
} else if (command == "menu") {
OnKeyboardEvent(KEY_MENU, button_state == "down");
- } else if (command == "volumemute") {
- OnKeyboardEvent(KEY_MUTE, button_state == "down");
} else if (command == "volumedown") {
OnKeyboardEvent(KEY_VOLUMEDOWN, button_state == "down");
} else if (command == "volumeup") {
@@ -327,11 +343,10 @@
void OnBluetoothChannelOpen(std::function<bool(const uint8_t *, size_t)>
bluetooth_message_sender) override {
LOG(VERBOSE) << "Bluetooth channel open";
+ auto config = cuttlefish::CuttlefishConfig::Get();
+ CHECK(config) << "Failed to get config";
bluetooth_handler_.reset(new cuttlefish::webrtc_streaming::BluetoothHandler(
- cuttlefish::CuttlefishConfig::Get()
- ->ForDefaultInstance()
- .rootcanal_test_port(),
- bluetooth_message_sender));
+ config->rootcanal_test_port(), bluetooth_message_sender));
}
void OnBluetoothMessage(const uint8_t *msg, size_t size) override {
@@ -355,107 +370,6 @@
std::weak_ptr<DisplayHandler> weak_display_handler_;
std::set<int32_t> active_touch_slots_;
cuttlefish::CameraController *camera_controller_;
-};
-
-class ConnectionObserverDemuxer
- : public cuttlefish::webrtc_streaming::ConnectionObserver {
- public:
- ConnectionObserverDemuxer(
- /* params for the base class */
- cuttlefish::InputSockets &input_sockets,
- cuttlefish::KernelLogEventsHandler *kernel_log_events_handler,
- std::map<std::string, cuttlefish::SharedFD>
- commands_to_custom_action_servers,
- std::weak_ptr<DisplayHandler> display_handler,
- CameraController *camera_controller,
- /* params for this class */
- cuttlefish::confui::HostVirtualInput &confui_input)
- : android_input_(input_sockets, kernel_log_events_handler,
- commands_to_custom_action_servers, display_handler,
- camera_controller),
- confui_input_{confui_input} {}
- virtual ~ConnectionObserverDemuxer() = default;
-
- void OnConnected(std::function<void(const uint8_t *, size_t, bool)>
- ctrl_msg_sender) override {
- android_input_.OnConnected(ctrl_msg_sender);
- }
-
- void OnTouchEvent(const std::string &label, int x, int y,
- bool down) override {
- if (confui_input_.IsConfUiActive()) {
- ConfUiLog(DEBUG) << "touch event ignored in confirmation UI mode";
- return;
- }
- android_input_.OnTouchEvent(label, x, y, down);
- }
-
- void OnMultiTouchEvent(const std::string &label, Json::Value id,
- Json::Value slot, Json::Value x, Json::Value y,
- bool down, int size) override {
- if (confui_input_.IsConfUiActive()) {
- ConfUiLog(DEBUG) << "multi-touch event ignored in confirmation UI mode";
- return;
- }
- android_input_.OnMultiTouchEvent(label, id, slot, x, y, down, size);
- }
-
- void OnKeyboardEvent(uint16_t code, bool down) override {
- if (confui_input_.IsConfUiActive()) {
- switch (code) {
- case KEY_POWER:
- confui_input_.PressConfirmButton(down);
- break;
- case KEY_MENU:
- confui_input_.PressCancelButton(down);
- break;
- default:
- ConfUiLog(DEBUG) << "key" << code
- << "is ignored in confirmation UI mode";
- break;
- }
- return;
- }
- android_input_.OnKeyboardEvent(code, down);
- }
-
- void OnSwitchEvent(uint16_t code, bool state) override {
- android_input_.OnSwitchEvent(code, state);
- }
-
- void OnAdbChannelOpen(std::function<bool(const uint8_t *, size_t)>
- adb_message_sender) override {
- android_input_.OnAdbChannelOpen(adb_message_sender);
- }
-
- void OnAdbMessage(const uint8_t *msg, size_t size) override {
- android_input_.OnAdbMessage(msg, size);
- }
-
- void OnControlChannelOpen(
- std::function<bool(const Json::Value)> control_message_sender) override {
- android_input_.OnControlChannelOpen(control_message_sender);
- }
-
- void OnControlMessage(const uint8_t *msg, size_t size) override {
- android_input_.OnControlMessage(msg, size);
- }
-
- void OnBluetoothChannelOpen(std::function<bool(const uint8_t *, size_t)>
- bluetooth_message_sender) override {
- android_input_.OnBluetoothChannelOpen(bluetooth_message_sender);
- }
-
- void OnBluetoothMessage(const uint8_t *msg, size_t size) override {
- android_input_.OnBluetoothMessage(msg, size);
- }
-
- void OnCameraData(const std::vector<char> &data) override {
- android_input_.OnCameraData(data);
- }
-
- private:
- ConnectionObserverForAndroid android_input_;
cuttlefish::confui::HostVirtualInput &confui_input_;
};
@@ -470,10 +384,10 @@
std::shared_ptr<cuttlefish::webrtc_streaming::ConnectionObserver>
CfConnectionObserverFactory::CreateObserver() {
return std::shared_ptr<cuttlefish::webrtc_streaming::ConnectionObserver>(
- new ConnectionObserverDemuxer(input_sockets_, kernel_log_events_handler_,
- commands_to_custom_action_servers_,
- weak_display_handler_, camera_controller_,
- confui_input_));
+ new ConnectionObserverImpl(input_sockets_, kernel_log_events_handler_,
+ commands_to_custom_action_servers_,
+ weak_display_handler_, camera_controller_,
+ confui_input_));
}
void CfConnectionObserverFactory::AddCustomActionServer(
diff --git a/host/frontend/webrtc/display_handler.cpp b/host/frontend/webrtc/display_handler.cpp
index 334c35f..3de1516 100644
--- a/host/frontend/webrtc/display_handler.cpp
+++ b/host/frontend/webrtc/display_handler.cpp
@@ -91,19 +91,4 @@
display_sinks_[buffer_display]->OnFrame(buffer, time_stamp);
}
}
-
-void DisplayHandler::IncClientCount() {
- client_count_++;
- if (client_count_ == 1) {
- screen_connector_.ReportClientsConnected(true);
- }
-}
-
-void DisplayHandler::DecClientCount() {
- client_count_--;
- if (client_count_ == 0) {
- screen_connector_.ReportClientsConnected(false);
- }
-}
-
} // namespace cuttlefish
diff --git a/host/frontend/webrtc/display_handler.h b/host/frontend/webrtc/display_handler.h
index 7703b08..1411984 100644
--- a/host/frontend/webrtc/display_handler.h
+++ b/host/frontend/webrtc/display_handler.h
@@ -60,9 +60,6 @@
[[noreturn]] void Loop();
void SendLastFrame();
- void IncClientCount();
- void DecClientCount();
-
private:
GenerateProcessedFrameCallback GetScreenConnectorCallback();
std::vector<std::shared_ptr<webrtc_streaming::VideoSink>> display_sinks_;
@@ -71,6 +68,5 @@
std::uint32_t last_buffer_display_ = 0;
std::mutex last_buffer_mutex_;
std::mutex next_frame_mutex_;
- int client_count_ = 0;
};
} // namespace cuttlefish
diff --git a/host/frontend/webrtc/lib/camera_streamer.cpp b/host/frontend/webrtc/lib/camera_streamer.cpp
index 7634e28..c9c7cf0 100644
--- a/host/frontend/webrtc/lib/camera_streamer.cpp
+++ b/host/frontend/webrtc/lib/camera_streamer.cpp
@@ -23,7 +23,7 @@
namespace webrtc_streaming {
CameraStreamer::CameraStreamer(unsigned int port, unsigned int cid)
- : cid_(cid), port_(port) {}
+ : cid_(cid), port_(port), camera_session_active_(false) {}
CameraStreamer::~CameraStreamer() { Disconnect(); }
@@ -47,9 +47,10 @@
LOG(INFO) << "Connected!";
}
auto resolution = resolution_.load();
- if (resolution.height <= 0 || resolution.width <= 0) {
- // We don't have a valid resolution that is necessary for
- // potential frame scaling
+ if (resolution.height <= 0 || resolution.width <= 0 ||
+ !camera_session_active_.load()) {
+ // Nobody is receiving frames or we don't have a valid resolution that is
+ // necessary for potential frame scaling
return;
}
auto frame = client_frame.video_frame_buffer()->ToI420().get();
@@ -142,7 +143,16 @@
}
reader_thread_ = std::thread([this] {
while (cvd_connection_.IsConnected()) {
+ static constexpr auto kEventKey = "event";
+ static constexpr auto kMessageStart =
+ "VIRTUAL_DEVICE_START_CAMERA_SESSION";
+ static constexpr auto kMessageStop = "VIRTUAL_DEVICE_STOP_CAMERA_SESSION";
auto json_value = cvd_connection_.ReadJsonMessage();
+ if (json_value[kEventKey] == kMessageStart) {
+ camera_session_active_ = true;
+ } else if (json_value[kEventKey] == kMessageStop) {
+ camera_session_active_ = false;
+ }
if (!json_value.empty()) {
SendMessage(json_value);
}
diff --git a/host/frontend/webrtc/lib/camera_streamer.h b/host/frontend/webrtc/lib/camera_streamer.h
index ceab2e6..3afed62 100644
--- a/host/frontend/webrtc/lib/camera_streamer.h
+++ b/host/frontend/webrtc/lib/camera_streamer.h
@@ -67,6 +67,7 @@
unsigned int cid_;
unsigned int port_;
std::thread reader_thread_;
+ std::atomic<bool> camera_session_active_;
};
} // namespace webrtc_streaming
diff --git a/host/frontend/webrtc/lib/client_handler.cpp b/host/frontend/webrtc/lib/client_handler.cpp
index 4893f8d..9be1ca0 100644
--- a/host/frontend/webrtc/lib/client_handler.cpp
+++ b/host/frontend/webrtc/lib/client_handler.cpp
@@ -103,6 +103,39 @@
} // namespace
+// Video streams initiating in the client may be added and removed at unexpected
+// times, causing the webrtc objects to be destroyed and created every time.
+// This class hides away that complexity and allows to set up sinks only once.
+class ClientVideoTrackImpl : public ClientVideoTrackInterface {
+ public:
+ void AddOrUpdateSink(rtc::VideoSinkInterface<webrtc::VideoFrame> *sink,
+ const rtc::VideoSinkWants &wants) override {
+ sink_ = sink;
+ wants_ = wants;
+ if (video_track_) {
+ video_track_->AddOrUpdateSink(sink, wants);
+ }
+ }
+
+ void SetVideoTrack(webrtc::VideoTrackInterface *track) {
+ video_track_ = track;
+ if (sink_) {
+ video_track_->AddOrUpdateSink(sink_, wants_);
+ }
+ }
+
+ void UnsetVideoTrack(webrtc::VideoTrackInterface *track) {
+ if (track == video_track_) {
+ video_track_ = nullptr;
+ }
+ }
+
+ private:
+ webrtc::VideoTrackInterface* video_track_;
+ rtc::VideoSinkInterface<webrtc::VideoFrame> *sink_ = nullptr;
+ rtc::VideoSinkWants wants_ = {};
+};
+
class InputChannelHandler : public webrtc::DataChannelObserver {
public:
InputChannelHandler(
@@ -439,7 +472,8 @@
: client_id_(client_id),
observer_(observer),
send_to_client_(send_to_client_cb),
- on_connection_changed_cb_(on_connection_changed_cb) {}
+ on_connection_changed_cb_(on_connection_changed_cb),
+ camera_track_(new ClientVideoTrackImpl()) {}
ClientHandler::~ClientHandler() {
for (auto &data_channel : data_channels_) {
@@ -499,15 +533,8 @@
return true;
}
-webrtc::VideoTrackInterface *ClientHandler::GetCameraStream() const {
- for (const auto &tranceiver : peer_connection_->GetTransceivers()) {
- auto track = tranceiver->receiver()->track();
- if (track &&
- track->kind() == webrtc::MediaStreamTrackInterface::kVideoKind) {
- return static_cast<webrtc::VideoTrackInterface *>(track.get());
- }
- }
- return nullptr;
+ClientVideoTrackInterface* ClientHandler::GetCameraStream() {
+ return camera_track_.get();
}
void ClientHandler::LogAndReplyError(const std::string &error_msg) const {
@@ -518,10 +545,24 @@
send_to_client_(reply);
}
+void ClientHandler::AddPendingIceCandidates() {
+ // Add any ice candidates that arrived before the remote description
+ for (auto& candidate: pending_ice_candidates_) {
+ peer_connection_->AddIceCandidate(std::move(candidate),
+ [this](webrtc::RTCError error) {
+ if (!error.ok()) {
+ LogAndReplyError(error.message());
+ }
+ });
+ }
+ pending_ice_candidates_.clear();
+}
+
void ClientHandler::OnCreateSDPSuccess(
webrtc::SessionDescriptionInterface *desc) {
std::string offer_str;
desc->ToString(&offer_str);
+ std::string sdp_type = desc->type();
peer_connection_->SetLocalDescription(
// The peer connection wraps this raw pointer with a scoped_refptr, so
// it's guaranteed to be deleted at some point
@@ -533,7 +574,7 @@
desc = nullptr;
Json::Value reply;
- reply["type"] = "offer";
+ reply["type"] = sdp_type;
reply["sdp"] = offer_str;
state_ = State::kAwaitingAnswer;
@@ -582,6 +623,42 @@
webrtc::PeerConnectionInterface::RTCOfferAnswerOptions());
// The created offer wil be sent to the client on
// OnSuccess(webrtc::SessionDescriptionInterface* desc)
+ } else if (type == "offer") {
+ auto result = ValidationResult::ValidateJsonObject(
+ message, type, {{"sdp", Json::ValueType::stringValue}});
+ if (!result.ok()) {
+ LogAndReplyError(result.error());
+ return;
+ }
+ auto remote_desc_str = message["sdp"].asString();
+ auto remote_desc = webrtc::CreateSessionDescription(
+ webrtc::SdpType::kOffer, remote_desc_str, nullptr /*error*/);
+ if (!remote_desc) {
+ LogAndReplyError("Failed to parse answer.");
+ return;
+ }
+
+ rtc::scoped_refptr<webrtc::SetRemoteDescriptionObserverInterface> observer(
+ new rtc::RefCountedObject<
+ CvdOnSetRemoteDescription>([this](webrtc::RTCError error) {
+ if (!error.ok()) {
+ LogAndReplyError(error.message());
+ // The remote description was rejected, this client can't be
+ // trusted anymore.
+ Close();
+ return;
+ }
+ remote_description_added_ = true;
+ AddPendingIceCandidates();
+ peer_connection_->CreateAnswer(
+ // No memory leak here because this is a ref counted objects and
+ // the peer connection immediately wraps it with a scoped_refptr
+ new rtc::RefCountedObject<CvdCreateSessionDescriptionObserver>(
+ weak_from_this()),
+ webrtc::PeerConnectionInterface::RTCOfferAnswerOptions());
+ }));
+ peer_connection_->SetRemoteDescription(std::move(remote_desc), observer);
+ state_ = State::kConnecting;
} else if (type == "answer") {
if (state_ != State::kAwaitingAnswer) {
LogAndReplyError("Received unexpected SDP answer");
@@ -611,6 +688,8 @@
}
}));
peer_connection_->SetRemoteDescription(std::move(remote_desc), observer);
+ remote_description_added_ = true;
+ AddPendingIceCandidates();
state_ = State::kConnecting;
} else if (type == "ice-candidate") {
@@ -648,12 +727,21 @@
LogAndReplyError("Failed to parse ICE candidate");
return;
}
- peer_connection_->AddIceCandidate(std::move(candidate),
- [this](webrtc::RTCError error) {
- if (!error.ok()) {
- LogAndReplyError(error.message());
- }
- });
+ if (remote_description_added_) {
+ peer_connection_->AddIceCandidate(std::move(candidate),
+ [this](webrtc::RTCError error) {
+ if (!error.ok()) {
+ LogAndReplyError(error.message());
+ }
+ });
+ } else {
+ // Store the ice candidate to be added later if it arrives before the
+ // remote description. This could happen if the client uses polling
+ // instead of websockets because the candidates are generated immediately
+ // after the remote (offer) description is set and the events and the ajax
+ // calls are asynchronous.
+ pending_ice_candidates_.push_back(std::move(candidate));
+ }
} else {
LogAndReplyError("Unknown client message type: " + type);
return;
@@ -820,11 +908,22 @@
}
void ClientHandler::OnTrack(
rtc::scoped_refptr<webrtc::RtpTransceiverInterface> transceiver) {
- // ignore
+ auto track = transceiver->receiver()->track();
+ if (track && track->kind() == webrtc::MediaStreamTrackInterface::kVideoKind) {
+ // It's ok to take the raw pointer here because we make sure to unset it
+ // when the track is removed
+ camera_track_->SetVideoTrack(
+ static_cast<webrtc::VideoTrackInterface *>(track.get()));
+ }
}
void ClientHandler::OnRemoveTrack(
rtc::scoped_refptr<webrtc::RtpReceiverInterface> receiver) {
- // ignore
+ auto track = receiver->track();
+ if (track && track->kind() == webrtc::MediaStreamTrackInterface::kVideoKind) {
+ // this only unsets if the track matches the one already in store
+ camera_track_->UnsetVideoTrack(
+ reinterpret_cast<webrtc::VideoTrackInterface *>(track.get()));
+ }
}
} // namespace webrtc_streaming
diff --git a/host/frontend/webrtc/lib/client_handler.h b/host/frontend/webrtc/lib/client_handler.h
index f7f587b..ea58552 100644
--- a/host/frontend/webrtc/lib/client_handler.h
+++ b/host/frontend/webrtc/lib/client_handler.h
@@ -40,6 +40,9 @@
class BluetoothChannelHandler;
class CameraChannelHandler;
+class ClientVideoTrackInterface;
+class ClientVideoTrackImpl;
+
class ClientHandler : public webrtc::PeerConnectionObserver,
public std::enable_shared_from_this<ClientHandler> {
public:
@@ -58,7 +61,7 @@
bool AddAudio(rtc::scoped_refptr<webrtc::AudioTrackInterface> track,
const std::string& label);
- webrtc::VideoTrackInterface* GetCameraStream() const;
+ ClientVideoTrackInterface* GetCameraStream();
void HandleMessage(const Json::Value& client_message);
@@ -117,6 +120,7 @@
void Close();
void LogAndReplyError(const std::string& error_msg) const;
+ void AddPendingIceCandidates();
int client_id_;
State state_ = State::kNew;
@@ -130,6 +134,18 @@
std::unique_ptr<ControlChannelHandler> control_handler_;
std::unique_ptr<BluetoothChannelHandler> bluetooth_handler_;
std::unique_ptr<CameraChannelHandler> camera_data_handler_;
+ std::unique_ptr<ClientVideoTrackImpl> camera_track_;
+ bool remote_description_added_ = false;
+ std::vector<std::unique_ptr<webrtc::IceCandidateInterface>>
+ pending_ice_candidates_;
+};
+
+class ClientVideoTrackInterface {
+ public:
+ virtual ~ClientVideoTrackInterface() = default;
+ virtual void AddOrUpdateSink(
+ rtc::VideoSinkInterface<webrtc::VideoFrame>* sink,
+ const rtc::VideoSinkWants& wants) = 0;
};
} // namespace webrtc_streaming
diff --git a/host/frontend/webrtc/lib/server_connection.cpp b/host/frontend/webrtc/lib/server_connection.cpp
new file mode 100644
index 0000000..314b6df
--- /dev/null
+++ b/host/frontend/webrtc/lib/server_connection.cpp
@@ -0,0 +1,650 @@
+//
+// Copyright (C) 2020 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+//
+
+#include "host/frontend/webrtc/lib/server_connection.h"
+
+#include <android-base/logging.h>
+#include <libwebsockets.h>
+
+#include "common/libs/fs/shared_fd.h"
+#include "common/libs/fs/shared_select.h"
+#include "common/libs/utils/files.h"
+
+namespace cuttlefish {
+namespace webrtc_streaming {
+
+// ServerConnection over Unix socket
+class UnixServerConnection : public ServerConnection {
+ public:
+ UnixServerConnection(const std::string& addr,
+ std::weak_ptr<ServerConnectionObserver> observer);
+ ~UnixServerConnection() override;
+
+ bool Send(const Json::Value& msg) override;
+
+ private:
+ void Connect() override;
+ void StopThread();
+ void ReadLoop();
+
+ const std::string addr_;
+ SharedFD conn_;
+ std::mutex write_mtx_;
+ std::weak_ptr<ServerConnectionObserver> observer_;
+ // The event fd must be declared before the thread to ensure it's initialized
+ // before the thread starts and is safe to be accessed from it.
+ SharedFD thread_notifier_;
+ std::atomic_bool running_ = false;
+ std::thread thread_;
+};
+
+// ServerConnection using websockets
+class WsConnectionContext;
+
+class WsConnection : public std::enable_shared_from_this<WsConnection> {
+ public:
+ struct CreateConnectionSul {
+ lws_sorted_usec_list_t sul = {};
+ std::weak_ptr<WsConnection> weak_this;
+ };
+
+ WsConnection(int port, const std::string& addr, const std::string& path,
+ ServerConfig::Security secure,
+ const std::vector<std::pair<std::string, std::string>>& headers,
+ std::weak_ptr<ServerConnectionObserver> observer,
+ std::shared_ptr<WsConnectionContext> context);
+
+ ~WsConnection();
+
+ void Connect();
+ bool Send(const Json::Value& msg);
+
+ void ConnectInner();
+
+ void OnError(const std::string& error);
+ void OnReceive(const uint8_t* data, size_t len, bool is_binary);
+ void OnOpen();
+ void OnClose();
+ void OnWriteable();
+
+ void AddHttpHeaders(unsigned char** p, unsigned char* end) const;
+
+ private:
+ struct WsBuffer {
+ WsBuffer() = default;
+ WsBuffer(const uint8_t* data, size_t len, bool binary)
+ : buffer_(LWS_PRE + len), is_binary_(binary) {
+ memcpy(&buffer_[LWS_PRE], data, len);
+ }
+
+ uint8_t* data() { return &buffer_[LWS_PRE]; }
+ bool is_binary() const { return is_binary_; }
+ size_t size() const { return buffer_.size() - LWS_PRE; }
+
+ private:
+ std::vector<uint8_t> buffer_;
+ bool is_binary_;
+ };
+ bool Send(const uint8_t* data, size_t len, bool binary = false);
+
+ CreateConnectionSul extended_sul_;
+ struct lws* wsi_;
+ const int port_;
+ const std::string addr_;
+ const std::string path_;
+ const ServerConfig::Security security_;
+ const std::vector<std::pair<std::string, std::string>> headers_;
+
+ std::weak_ptr<ServerConnectionObserver> observer_;
+
+ // each element contains the data to be sent and whether it's binary or not
+ std::deque<WsBuffer> write_queue_;
+ std::mutex write_queue_mutex_;
+ // The connection object should not outlive the context object. This reference
+ // guarantees it.
+ std::shared_ptr<WsConnectionContext> context_;
+};
+
+class WsConnectionContext
+ : public std::enable_shared_from_this<WsConnectionContext> {
+ public:
+ static std::shared_ptr<WsConnectionContext> Create();
+
+ WsConnectionContext(struct lws_context* lws_ctx);
+ ~WsConnectionContext();
+
+ std::unique_ptr<ServerConnection> CreateConnection(
+ int port, const std::string& addr, const std::string& path,
+ ServerConfig::Security secure,
+ std::weak_ptr<ServerConnectionObserver> observer,
+ const std::vector<std::pair<std::string, std::string>>& headers);
+
+ void RememberConnection(void*, std::weak_ptr<WsConnection>);
+ void ForgetConnection(void*);
+ std::shared_ptr<WsConnection> GetConnection(void*);
+
+ struct lws_context* lws_context() {
+ return lws_context_;
+ }
+
+ private:
+ void Start();
+
+ std::map<void*, std::weak_ptr<WsConnection>> weak_by_ptr_;
+ std::mutex map_mutex_;
+ struct lws_context* lws_context_;
+ std::thread message_loop_;
+};
+
+std::unique_ptr<ServerConnection> ServerConnection::Connect(
+ const ServerConfig& conf,
+ std::weak_ptr<ServerConnectionObserver> observer) {
+ std::unique_ptr<ServerConnection> ret;
+ // If the provided address points to an existing UNIX socket in the file
+ // system connect to it, otherwise assume it's a network address and connect
+ // using websockets
+ if (FileIsSocket(conf.addr)) {
+ ret.reset(new UnixServerConnection(conf.addr, observer));
+ } else {
+ // This can be a local variable since the ws connection will keep a
+ // reference to it.
+ auto ws_context = WsConnectionContext::Create();
+ CHECK(ws_context) << "Failed to create websocket context";
+ ret = ws_context->CreateConnection(conf.port, conf.addr, conf.path,
+ conf.security, observer,
+ conf.http_headers);
+ }
+ ret->Connect();
+ return ret;
+}
+
+void ServerConnection::Reconnect() { Connect(); }
+
+// UnixServerConnection implementation
+
+UnixServerConnection::UnixServerConnection(
+ const std::string& addr, std::weak_ptr<ServerConnectionObserver> observer)
+ : addr_(addr), observer_(observer) {}
+
+UnixServerConnection::~UnixServerConnection() {
+ StopThread();
+}
+
+bool UnixServerConnection::Send(const Json::Value& msg) {
+ Json::StreamWriterBuilder factory;
+ auto str = Json::writeString(factory, msg);
+ std::lock_guard<std::mutex> lock(write_mtx_);
+ auto res =
+ conn_->Send(reinterpret_cast<const uint8_t*>(str.c_str()), str.size(), 0);
+ if (res < 0) {
+ LOG(ERROR) << "Failed to send data to signaling server: "
+ << conn_->StrError();
+ // Don't call OnError() here, the receiving thread probably did it already
+ // or is about to do it.
+ }
+ // A SOCK_SEQPACKET unix socket will send the entire message or fail, but it
+ // won't send a partial message.
+ return res == str.size();
+}
+
+void UnixServerConnection::Connect() {
+ // The thread could be running if this is a Reconnect
+ StopThread();
+
+ conn_ = SharedFD::SocketLocalClient(addr_, false, SOCK_SEQPACKET);
+ if (!conn_->IsOpen()) {
+ LOG(ERROR) << "Failed to connect to unix socket: " << conn_->StrError();
+ if (auto o = observer_.lock(); o) {
+ o->OnError("Failed to connect to unix socket");
+ }
+ return;
+ }
+ thread_notifier_ = SharedFD::Event();
+ if (!thread_notifier_->IsOpen()) {
+ LOG(ERROR) << "Failed to create eventfd for background thread: "
+ << thread_notifier_->StrError();
+ if (auto o = observer_.lock(); o) {
+ o->OnError("Failed to create eventfd for background thread");
+ }
+ return;
+ }
+ if (auto o = observer_.lock(); o) {
+ o->OnOpen();
+ }
+ // Start the thread
+ running_ = true;
+ thread_ = std::thread([this](){ReadLoop();});
+}
+
+void UnixServerConnection::StopThread() {
+ running_ = false;
+ if (!thread_notifier_->IsOpen()) {
+ // The thread won't be running if this isn't open
+ return;
+ }
+ if (thread_notifier_->EventfdWrite(1) < 0) {
+ LOG(ERROR) << "Failed to notify background thread, this thread may block";
+ }
+ if (thread_.joinable()) {
+ thread_.join();
+ }
+}
+
+void UnixServerConnection::ReadLoop() {
+ if (!thread_notifier_->IsOpen()) {
+ LOG(ERROR) << "The UnixServerConnection's background thread is unable to "
+ "receive notifications so it can't run";
+ return;
+ }
+ std::vector<uint8_t> buffer(4096, 0);
+ while (running_) {
+ SharedFDSet rset;
+ rset.Set(thread_notifier_);
+ rset.Set(conn_);
+ auto res = Select(&rset, nullptr, nullptr, nullptr);
+ if (res < 0) {
+ LOG(ERROR) << "Failed to select from background thread";
+ break;
+ }
+ if (rset.IsSet(thread_notifier_)) {
+ eventfd_t val;
+ auto res = thread_notifier_->EventfdRead(&val);
+ if (res < 0) {
+ LOG(ERROR) << "Error reading from event fd: "
+ << thread_notifier_->StrError();
+ break;
+ }
+ }
+ if (rset.IsSet(conn_)) {
+ auto size = conn_->Recv(buffer.data(), 0, MSG_TRUNC | MSG_PEEK);
+ if (size > buffer.size()) {
+ // Enlarge enough to accommodate size bytes and be a multiple of 4096
+ auto new_size = (size + 4095) & ~4095;
+ buffer.resize(new_size);
+ }
+ auto res = conn_->Recv(buffer.data(), buffer.size(), MSG_TRUNC);
+ if (res < 0) {
+ LOG(ERROR) << "Failed to read from server: " << conn_->StrError();
+ if (auto observer = observer_.lock(); observer) {
+ observer->OnError(conn_->StrError());
+ }
+ return;
+ }
+ if (res == 0) {
+ auto observer = observer_.lock();
+ if (observer) {
+ observer->OnClose();
+ }
+ break;
+ }
+ auto observer = observer_.lock();
+ if (observer) {
+ observer->OnReceive(buffer.data(), res, false);
+ }
+ }
+ }
+}
+
+// WsConnection implementation
+
+int LwsCallback(struct lws* wsi, enum lws_callback_reasons reason, void* user,
+ void* in, size_t len);
+void CreateConnectionCallback(lws_sorted_usec_list_t* sul);
+
+namespace {
+
+constexpr char kProtocolName[] = "cf-webrtc-device";
+constexpr int kBufferSize = 65536;
+
+const uint32_t backoff_ms[] = {1000, 2000, 3000, 4000, 5000};
+
+const lws_retry_bo_t kRetry = {
+ .retry_ms_table = backoff_ms,
+ .retry_ms_table_count = LWS_ARRAY_SIZE(backoff_ms),
+ .conceal_count = LWS_ARRAY_SIZE(backoff_ms),
+
+ .secs_since_valid_ping = 3, /* force PINGs after secs idle */
+ .secs_since_valid_hangup = 10, /* hangup after secs idle */
+
+ .jitter_percent = 20,
+};
+
+const struct lws_protocols kProtocols[2] = {
+ {kProtocolName, LwsCallback, 0, kBufferSize, 0, NULL, 0},
+ {NULL, NULL, 0, 0, 0, NULL, 0}};
+
+} // namespace
+
+std::shared_ptr<WsConnectionContext> WsConnectionContext::Create() {
+ struct lws_context_creation_info context_info = {};
+ context_info.port = CONTEXT_PORT_NO_LISTEN;
+ context_info.options = LWS_SERVER_OPTION_DO_SSL_GLOBAL_INIT;
+ context_info.protocols = kProtocols;
+ struct lws_context* lws_ctx = lws_create_context(&context_info);
+ if (!lws_ctx) {
+ return nullptr;
+ }
+ return std::shared_ptr<WsConnectionContext>(new WsConnectionContext(lws_ctx));
+}
+
+WsConnectionContext::WsConnectionContext(struct lws_context* lws_ctx)
+ : lws_context_(lws_ctx) {
+ Start();
+}
+
+WsConnectionContext::~WsConnectionContext() {
+ lws_context_destroy(lws_context_);
+ if (message_loop_.joinable()) {
+ message_loop_.join();
+ }
+}
+
+void WsConnectionContext::Start() {
+ message_loop_ = std::thread([this]() {
+ for (;;) {
+ if (lws_service(lws_context_, 0) < 0) {
+ break;
+ }
+ }
+ });
+}
+
+// This wrapper is needed because the ServerConnection objects are meant to be
+// referenced by std::unique_ptr but WsConnection needs to be referenced by
+// std::shared_ptr because it's also (weakly) referenced by the websocket
+// thread.
+class WsConnectionWrapper : public ServerConnection {
+ public:
+ WsConnectionWrapper(std::shared_ptr<WsConnection> conn) : conn_(conn) {}
+
+ bool Send(const Json::Value& msg) override { return conn_->Send(msg); }
+
+ private:
+ void Connect() override { return conn_->Connect(); }
+ std::shared_ptr<WsConnection> conn_;
+};
+
+std::unique_ptr<ServerConnection> WsConnectionContext::CreateConnection(
+ int port, const std::string& addr, const std::string& path,
+ ServerConfig::Security security,
+ std::weak_ptr<ServerConnectionObserver> observer,
+ const std::vector<std::pair<std::string, std::string>>& headers) {
+ return std::unique_ptr<ServerConnection>(
+ new WsConnectionWrapper(std::make_shared<WsConnection>(
+ port, addr, path, security, headers, observer, shared_from_this())));
+}
+
+std::shared_ptr<WsConnection> WsConnectionContext::GetConnection(void* raw) {
+ std::shared_ptr<WsConnection> connection;
+ {
+ std::lock_guard<std::mutex> lock(map_mutex_);
+ if (weak_by_ptr_.count(raw) == 0) {
+ return nullptr;
+ }
+ connection = weak_by_ptr_[raw].lock();
+ if (!connection) {
+ weak_by_ptr_.erase(raw);
+ }
+ }
+ return connection;
+}
+
+void WsConnectionContext::RememberConnection(void* raw,
+ std::weak_ptr<WsConnection> conn) {
+ std::lock_guard<std::mutex> lock(map_mutex_);
+ weak_by_ptr_.emplace(
+ std::pair<void*, std::weak_ptr<WsConnection>>(raw, conn));
+}
+
+void WsConnectionContext::ForgetConnection(void* raw) {
+ std::lock_guard<std::mutex> lock(map_mutex_);
+ weak_by_ptr_.erase(raw);
+}
+
+WsConnection::WsConnection(
+ int port, const std::string& addr, const std::string& path,
+ ServerConfig::Security security,
+ const std::vector<std::pair<std::string, std::string>>& headers,
+ std::weak_ptr<ServerConnectionObserver> observer,
+ std::shared_ptr<WsConnectionContext> context)
+ : port_(port),
+ addr_(addr),
+ path_(path),
+ security_(security),
+ headers_(headers),
+ observer_(observer),
+ context_(context) {}
+
+WsConnection::~WsConnection() {
+ context_->ForgetConnection(this);
+ // This will cause the callback to be called which will drop the connection
+ // after seeing the context doesn't remember this object
+ lws_callback_on_writable(wsi_);
+}
+
+void WsConnection::Connect() {
+ memset(&extended_sul_.sul, 0, sizeof(extended_sul_.sul));
+ extended_sul_.weak_this = weak_from_this();
+ lws_sul_schedule(context_->lws_context(), 0, &extended_sul_.sul,
+ CreateConnectionCallback, 1);
+}
+
+void WsConnection::AddHttpHeaders(unsigned char** p, unsigned char* end) const {
+ for (const auto& header_entry : headers_) {
+ const auto& name = header_entry.first;
+ const auto& value = header_entry.second;
+ auto res = lws_add_http_header_by_name(
+ wsi_, reinterpret_cast<const unsigned char*>(name.c_str()),
+ reinterpret_cast<const unsigned char*>(value.c_str()), value.size(), p,
+ end);
+ if (res != 0) {
+ LOG(ERROR) << "Unable to add header: " << name;
+ }
+ }
+ if (!headers_.empty()) {
+ // Let LWS know we added some headers.
+ lws_client_http_body_pending(wsi_, 1);
+ }
+}
+
+void WsConnection::OnError(const std::string& error) {
+ auto observer = observer_.lock();
+ if (observer) {
+ observer->OnError(error);
+ }
+}
+void WsConnection::OnReceive(const uint8_t* data, size_t len, bool is_binary) {
+ auto observer = observer_.lock();
+ if (observer) {
+ observer->OnReceive(data, len, is_binary);
+ }
+}
+void WsConnection::OnOpen() {
+ auto observer = observer_.lock();
+ if (observer) {
+ observer->OnOpen();
+ }
+}
+void WsConnection::OnClose() {
+ auto observer = observer_.lock();
+ if (observer) {
+ observer->OnClose();
+ }
+}
+
+void WsConnection::OnWriteable() {
+ WsBuffer buffer;
+ {
+ std::lock_guard<std::mutex> lock(write_queue_mutex_);
+ if (write_queue_.size() == 0) {
+ return;
+ }
+ buffer = std::move(write_queue_.front());
+ write_queue_.pop_front();
+ }
+ auto flags = lws_write_ws_flags(
+ buffer.is_binary() ? LWS_WRITE_BINARY : LWS_WRITE_TEXT, true, true);
+ auto res = lws_write(wsi_, buffer.data(), buffer.size(),
+ (enum lws_write_protocol)flags);
+ if (res != buffer.size()) {
+ LOG(WARNING) << "Unable to send the entire message!";
+ }
+}
+
+bool WsConnection::Send(const Json::Value& msg) {
+ Json::StreamWriterBuilder factory;
+ auto str = Json::writeString(factory, msg);
+ return Send(reinterpret_cast<const uint8_t*>(str.c_str()), str.size());
+}
+
+bool WsConnection::Send(const uint8_t* data, size_t len, bool binary) {
+ if (!wsi_) {
+ LOG(WARNING) << "Send called on an uninitialized connection!!";
+ return false;
+ }
+ WsBuffer buffer(data, len, binary);
+ {
+ std::lock_guard<std::mutex> lock(write_queue_mutex_);
+ write_queue_.emplace_back(std::move(buffer));
+ }
+
+ lws_callback_on_writable(wsi_);
+ return true;
+}
+
+int LwsCallback(struct lws* wsi, enum lws_callback_reasons reason, void* user,
+ void* in, size_t len) {
+ constexpr int DROP = -1;
+ constexpr int OK = 0;
+
+ // For some values of `reason`, `user` doesn't point to the value provided
+ // when the connection was created. This function object should be used with
+ // care.
+ auto with_connection =
+ [wsi, user](std::function<void(std::shared_ptr<WsConnection>)> cb) {
+ auto context = reinterpret_cast<WsConnectionContext*>(user);
+ auto connection = context->GetConnection(wsi);
+ if (!connection) {
+ return DROP;
+ }
+ cb(connection);
+ return OK;
+ };
+
+ switch (reason) {
+ case LWS_CALLBACK_CLIENT_CONNECTION_ERROR:
+ return with_connection([in](std::shared_ptr<WsConnection> connection) {
+ connection->OnError(in ? (char*)in : "(null)");
+ });
+
+ case LWS_CALLBACK_CLIENT_RECEIVE:
+ return with_connection(
+ [in, len, wsi](std::shared_ptr<WsConnection> connection) {
+ connection->OnReceive((const uint8_t*)in, len,
+ lws_frame_is_binary(wsi));
+ });
+
+ case LWS_CALLBACK_CLIENT_ESTABLISHED:
+ return with_connection([](std::shared_ptr<WsConnection> connection) {
+ connection->OnOpen();
+ });
+
+ case LWS_CALLBACK_CLIENT_CLOSED:
+ return with_connection([](std::shared_ptr<WsConnection> connection) {
+ connection->OnClose();
+ });
+
+ case LWS_CALLBACK_CLIENT_WRITEABLE:
+ return with_connection([](std::shared_ptr<WsConnection> connection) {
+ connection->OnWriteable();
+ });
+
+ case LWS_CALLBACK_CLIENT_APPEND_HANDSHAKE_HEADER:
+ return with_connection(
+ [in, len](std::shared_ptr<WsConnection> connection) {
+ auto p = reinterpret_cast<unsigned char**>(in);
+ auto end = (*p) + len;
+ connection->AddHttpHeaders(p, end);
+ });
+
+ case LWS_CALLBACK_CLIENT_HTTP_WRITEABLE:
+ // This callback is only called when we add additional HTTP headers, let
+ // LWS know we're done modifying the HTTP request.
+ lws_client_http_body_pending(wsi, 0);
+ return 0;
+
+ default:
+ LOG(VERBOSE) << "Unhandled value: " << reason;
+ return lws_callback_http_dummy(wsi, reason, user, in, len);
+ }
+}
+
+void CreateConnectionCallback(lws_sorted_usec_list_t* sul) {
+ std::shared_ptr<WsConnection> connection =
+ reinterpret_cast<WsConnection::CreateConnectionSul*>(sul)
+ ->weak_this.lock();
+ if (!connection) {
+ LOG(WARNING) << "The object was already destroyed by the time of the first "
+ << "connection attempt. That's unusual.";
+ return;
+ }
+ connection->ConnectInner();
+}
+
+void WsConnection::ConnectInner() {
+ struct lws_client_connect_info connect_info;
+
+ memset(&connect_info, 0, sizeof(connect_info));
+
+ connect_info.context = context_->lws_context();
+ connect_info.port = port_;
+ connect_info.address = addr_.c_str();
+ connect_info.path = path_.c_str();
+ connect_info.host = connect_info.address;
+ connect_info.origin = connect_info.address;
+ switch (security_) {
+ case ServerConfig::Security::kAllowSelfSigned:
+ connect_info.ssl_connection = LCCSCF_ALLOW_SELFSIGNED |
+ LCCSCF_SKIP_SERVER_CERT_HOSTNAME_CHECK |
+ LCCSCF_USE_SSL;
+ break;
+ case ServerConfig::Security::kStrict:
+ connect_info.ssl_connection = LCCSCF_USE_SSL;
+ break;
+ case ServerConfig::Security::kInsecure:
+ connect_info.ssl_connection = 0;
+ break;
+ }
+ connect_info.protocol = "webrtc-operator";
+ connect_info.local_protocol_name = kProtocolName;
+ connect_info.pwsi = &wsi_;
+ connect_info.retry_and_idle_policy = &kRetry;
+ // There is no guarantee the connection object still exists when the callback
+ // is called. Put the context instead as the user data which is guaranteed to
+ // still exist and holds a weak ptr to the connection.
+ connect_info.userdata = context_.get();
+
+ if (lws_client_connect_via_info(&connect_info)) {
+ // wsi_ is not initialized until after the call to
+ // lws_client_connect_via_info(). Luckily, this is guaranteed to run before
+ // the protocol callback is called because it runs in the same loop.
+ context_->RememberConnection(wsi_, weak_from_this());
+ } else {
+ LOG(ERROR) << "Connection failed!";
+ }
+}
+
+} // namespace webrtc_streaming
+} // namespace cuttlefish
diff --git a/host/frontend/webrtc/lib/server_connection.h b/host/frontend/webrtc/lib/server_connection.h
new file mode 100644
index 0000000..ab7111f
--- /dev/null
+++ b/host/frontend/webrtc/lib/server_connection.h
@@ -0,0 +1,93 @@
+//
+// Copyright (C) 2020 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+//
+
+#pragma once
+
+#include <string.h>
+
+#include <deque>
+#include <functional>
+#include <map>
+#include <memory>
+#include <mutex>
+#include <string>
+#include <thread>
+#include <vector>
+
+#include <json/json.h>
+#include <libwebsockets.h>
+
+namespace cuttlefish {
+namespace webrtc_streaming {
+
+struct ServerConfig {
+ enum class Security {
+ kInsecure,
+ kAllowSelfSigned,
+ kStrict,
+ };
+
+ // The ip address or domain name of the operator server.
+ std::string addr;
+ int port;
+ // The path component of the operator server's register url.
+ std::string path;
+ // The security level to use when connecting to the operator server.
+ Security security;
+ // A list of key value pairs to include as HTTP handshake headers when
+ // connecting to the operator.
+ std::vector<std::pair<std::string, std::string>> http_headers;
+};
+
+class ServerConnectionObserver {
+ public:
+ virtual ~ServerConnectionObserver() = default;
+ // Called when the connection to the server has been established. This is the
+ // cue to start using Send().
+ virtual void OnOpen() = 0;
+ virtual void OnClose() = 0;
+ // Called when the connection to the server has failed with an unrecoverable
+ // error.
+ virtual void OnError(const std::string& error) = 0;
+ virtual void OnReceive(const uint8_t* msg, size_t length, bool is_binary) = 0;
+};
+
+// Represents a connection to the signaling server. When a connection is created
+// it connects with the server automatically but sends no info.
+// Only Send() can be called from multiple threads simultaneously. Reconnect(),
+// Send() and the destructor will run into race conditions if called
+// concurrently.
+class ServerConnection {
+ public:
+ static std::unique_ptr<ServerConnection> Connect(
+ const ServerConfig& conf,
+ std::weak_ptr<ServerConnectionObserver> observer);
+
+ // Destroying the connection will disconnect from the signaling server and
+ // release any open fds.
+ virtual ~ServerConnection() = default;
+
+ // Sends data to the server encoded as JSON.
+ virtual bool Send(const Json::Value&) = 0;
+
+ // makes re-connect request
+ virtual void Reconnect();
+
+ private:
+ virtual void Connect() = 0;
+};
+
+} // namespace webrtc_streaming
+} // namespace cuttlefish
diff --git a/host/frontend/webrtc/lib/streamer.cpp b/host/frontend/webrtc/lib/streamer.cpp
index 71bbab5..088b25b 100644
--- a/host/frontend/webrtc/lib/streamer.cpp
+++ b/host/frontend/webrtc/lib/streamer.cpp
@@ -62,12 +62,10 @@
constexpr auto kControlPanelButtonHingeAngleValue = "hinge_angle_value";
constexpr auto kCustomControlPanelButtonsField = "custom_control_panel_buttons";
-void SendJson(WsConnection* ws_conn, const Json::Value& data) {
- Json::StreamWriterBuilder factory;
- auto data_str = Json::writeString(factory, data);
- ws_conn->Send(reinterpret_cast<const uint8_t*>(data_str.c_str()),
- data_str.size());
-}
+constexpr int kRegistrationRetries = 3;
+constexpr int kRetryFirstIntervalMs = 1000;
+constexpr int kReconnectRetries = 100;
+constexpr int kReconnectIntervalMs = 1000;
bool ParseMessage(const uint8_t* data, size_t length, Json::Value* msg_out) {
auto str = reinterpret_cast<const char*>(data);
@@ -136,10 +134,14 @@
} // namespace
-class Streamer::Impl : public WsConnectionObserver {
+
+class Streamer::Impl : public ServerConnectionObserver,
+ public std::enable_shared_from_this<ServerConnectionObserver> {
public:
std::shared_ptr<ClientHandler> CreateClientHandler(int client_id);
+ void Register(std::weak_ptr<OperatorObserver> observer);
+
void SendMessageToClient(int client_id, const Json::Value& msg);
void DestroyClientHandler(int client_id);
void SetupCameraForClient(int client_id);
@@ -157,7 +159,7 @@
// no need for extra synchronization mechanisms (mutex)
StreamerConfig config_;
OperatorServerConfig operator_config_;
- std::shared_ptr<WsConnection> server_connection_;
+ std::unique_ptr<ServerConnection> server_connection_;
std::shared_ptr<ConnectionObserverFactory> connection_observer_factory_;
rtc::scoped_refptr<webrtc::PeerConnectionFactoryInterface>
peer_connection_factory_;
@@ -173,6 +175,8 @@
std::vector<ControlPanelButtonDescriptor> custom_control_panel_buttons_;
std::shared_ptr<AudioDeviceModuleWrapper> audio_device_module_;
std::unique_ptr<CameraStreamer> camera_streamer_;
+ int registration_retries_left_ = kRegistrationRetries;
+ int retry_interval_ms_ = kRetryFirstIntervalMs;
};
Streamer::Streamer(std::unique_ptr<Streamer::Impl> impl)
@@ -307,22 +311,7 @@
// No need to block the calling thread on this, the observer will be notified
// when the connection is established.
impl_->signal_thread_->PostTask(RTC_FROM_HERE, [this, observer]() {
- impl_->operator_observer_ = observer;
- // This can be a local variable since the connection object will keep a
- // reference to it.
- auto ws_context = WsConnectionContext::Create();
- CHECK(ws_context) << "Failed to create websocket context";
- impl_->server_connection_ = ws_context->CreateConnection(
- impl_->config_.operator_server.port,
- impl_->config_.operator_server.addr,
- impl_->config_.operator_server.path,
- impl_->config_.operator_server.security, impl_,
- impl_->config_.operator_server.http_headers);
-
- CHECK(impl_->server_connection_)
- << "Unable to create websocket connection object";
-
- impl_->server_connection_->Connect();
+ impl_->Register(observer);
});
}
@@ -345,6 +334,21 @@
}
}
+void Streamer::Impl::Register(std::weak_ptr<OperatorObserver> observer) {
+ operator_observer_ = observer;
+ // When the connection is established the OnOpen function will be called where
+ // the registration will take place
+ if (!server_connection_) {
+ server_connection_ =
+ ServerConnection::Connect(config_.operator_server, weak_from_this());
+ } else {
+ // in case connection attempt is retried, just call Reconnect().
+ // Recreating server_connection_ object will destroy existing WSConnection
+ // object and task re-scheduling will fail
+ server_connection_->Reconnect();
+ }
+}
+
void Streamer::Impl::OnOpen() {
// Called from the websocket thread.
// Connected to operator.
@@ -354,6 +358,9 @@
cuttlefish::webrtc_signaling::kRegisterType;
register_obj[cuttlefish::webrtc_signaling::kDeviceIdField] =
config_.device_id;
+ CHECK(config_.client_files_port >= 0) << "Invalide device port provided";
+ register_obj[cuttlefish::webrtc_signaling::kDevicePortField] =
+ config_.client_files_port;
Json::Value device_info;
Json::Value displays(Json::ValueType::arrayValue);
@@ -409,7 +416,7 @@
}
device_info[kCustomControlPanelButtonsField] = custom_control_panel_buttons;
register_obj[cuttlefish::webrtc_signaling::kDeviceInfoField] = device_info;
- SendJson(server_connection_.get(), register_obj);
+ server_connection_->Send(register_obj);
// Do this last as OnRegistered() is user code and may take some time to
// complete (although it shouldn't...)
auto observer = operator_observer_.lock();
@@ -423,24 +430,45 @@
// Called from websocket thread
// The operator shouldn't close the connection with the client, it's up to the
// device to decide when to disconnect.
- LOG(WARNING) << "Websocket closed unexpectedly";
+ LOG(WARNING) << "Connection with server closed unexpectedly";
signal_thread_->PostTask(RTC_FROM_HERE, [this]() {
auto observer = operator_observer_.lock();
if (observer) {
observer->OnClose();
}
});
+ LOG(INFO) << "Trying to re-connect to operator..";
+ registration_retries_left_ = kReconnectRetries;
+ retry_interval_ms_ = kReconnectIntervalMs;
+ signal_thread_->PostDelayedTask(
+ RTC_FROM_HERE, [this]() { Register(operator_observer_); },
+ retry_interval_ms_);
}
void Streamer::Impl::OnError(const std::string& error) {
// Called from websocket thread.
- LOG(ERROR) << "Error on connection with the operator: " << error;
- signal_thread_->PostTask(RTC_FROM_HERE, [this]() {
- auto observer = operator_observer_.lock();
- if (observer) {
- observer->OnError();
- }
- });
+ if (registration_retries_left_) {
+ LOG(WARNING) << "Connection to operator failed (" << error << "), "
+ << registration_retries_left_ << " retries left"
+ << " (will retry in " << retry_interval_ms_ / 1000 << "s)";
+ --registration_retries_left_;
+ signal_thread_->PostDelayedTask(
+ RTC_FROM_HERE,
+ [this]() {
+ // Need to reconnect and register again with operator
+ Register(operator_observer_);
+ },
+ retry_interval_ms_);
+ retry_interval_ms_ *= 2;
+ } else {
+ LOG(ERROR) << "Error on connection with the operator: " << error;
+ signal_thread_->PostTask(RTC_FROM_HERE, [this]() {
+ auto observer = operator_observer_.lock();
+ if (observer) {
+ observer->OnError();
+ }
+ });
+ }
}
void Streamer::Impl::HandleConfigMessage(const Json::Value& server_message) {
@@ -635,8 +663,8 @@
cuttlefish::webrtc_signaling::kForwardType;
wrapper[cuttlefish::webrtc_signaling::kClientIdField] = client_id;
// This is safe to call from the webrtc threads because
- // WsConnection is thread safe
- SendJson(server_connection_.get(), wrapper);
+ // ServerConnection(s) are thread safe
+ server_connection_->Send(wrapper);
}
void Streamer::Impl::DestroyClientHandler(int client_id) {
diff --git a/host/frontend/webrtc/lib/streamer.h b/host/frontend/webrtc/lib/streamer.h
index 50a7e12..bc2297e 100644
--- a/host/frontend/webrtc/lib/streamer.h
+++ b/host/frontend/webrtc/lib/streamer.h
@@ -32,7 +32,7 @@
#include "host/frontend/webrtc/lib/connection_observer.h"
#include "host/frontend/webrtc/lib/local_recorder.h"
#include "host/frontend/webrtc/lib/video_sink.h"
-#include "host/frontend/webrtc/lib/ws_connection.h"
+#include "host/frontend/webrtc/lib/server_connection.h"
namespace cuttlefish {
namespace webrtc_streaming {
@@ -42,18 +42,9 @@
struct StreamerConfig {
// The id with which to register with the operator server.
std::string device_id;
- struct {
- // The ip address or domain name of the operator server.
- std::string addr;
- int port;
- // The path component of the operator server's register url.
- std::string path;
- // The security level to use when connecting to the operator server.
- WsConnection::Security security;
- // A list of key value pairs to include as HTTP handshake headers when
- // connecting to the operator.
- std::vector<std::pair<std::string, std::string>> http_headers;
- } operator_server;
+ // The port on which the client files are being served
+ int client_files_port;
+ ServerConfig operator_server;
// The port ranges webrtc is allowed to use.
// [0,0] means all ports
std::pair<uint16_t, uint16_t> udp_port_range = {15550, 15558};
diff --git a/host/frontend/webrtc/lib/utils.cpp b/host/frontend/webrtc/lib/utils.cpp
index 78460c3..e84b897 100644
--- a/host/frontend/webrtc/lib/utils.cpp
+++ b/host/frontend/webrtc/lib/utils.cpp
@@ -28,6 +28,9 @@
std::string ValidateField(const Json::Value &obj, const std::string &type,
const std::string &field_name,
const Json::ValueType &field_type, bool required) {
+ if (!obj.isObject()) {
+ return "Expected object with name-value pairs";
+ }
if (!obj.isMember(field_name) && !required) {
return "";
}
diff --git a/host/frontend/webrtc/lib/ws_connection.cpp b/host/frontend/webrtc/lib/ws_connection.cpp
deleted file mode 100644
index 5303c21..0000000
--- a/host/frontend/webrtc/lib/ws_connection.cpp
+++ /dev/null
@@ -1,446 +0,0 @@
-//
-// Copyright (C) 2020 The Android Open Source Project
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-//
-
-#include "host/frontend/webrtc/lib/ws_connection.h"
-
-#include <android-base/logging.h>
-#include <libwebsockets.h>
-
-class WsConnectionContextImpl;
-
-class WsConnectionImpl : public WsConnection,
- public std::enable_shared_from_this<WsConnectionImpl> {
- public:
- struct CreateConnectionSul {
- lws_sorted_usec_list_t sul = {};
- std::weak_ptr<WsConnectionImpl> weak_this;
- };
-
- WsConnectionImpl(
- int port, const std::string& addr, const std::string& path,
- Security secure,
- const std::vector<std::pair<std::string, std::string>>& headers,
- std::weak_ptr<WsConnectionObserver> observer,
- std::shared_ptr<WsConnectionContextImpl> context);
-
- ~WsConnectionImpl() override;
-
- void Connect() override;
- void ConnectInner();
-
- bool Send(const uint8_t* data, size_t len, bool binary = false) override;
-
- void OnError(const std::string& error);
- void OnReceive(const uint8_t* data, size_t len, bool is_binary);
- void OnOpen();
- void OnClose();
- void OnWriteable();
-
- void AddHttpHeaders(unsigned char** p, unsigned char* end) const;
-
- private:
- struct WsBuffer {
- WsBuffer() = default;
- WsBuffer(const uint8_t* data, size_t len, bool binary)
- : buffer_(LWS_PRE + len), is_binary_(binary) {
- memcpy(&buffer_[LWS_PRE], data, len);
- }
-
- uint8_t* data() { return &buffer_[LWS_PRE]; }
- bool is_binary() const { return is_binary_; }
- size_t size() const { return buffer_.size() - LWS_PRE; }
-
- private:
- std::vector<uint8_t> buffer_;
- bool is_binary_;
- };
-
- CreateConnectionSul extended_sul_;
- struct lws* wsi_;
- const int port_;
- const std::string addr_;
- const std::string path_;
- const Security security_;
- const std::vector<std::pair<std::string, std::string>> headers_;
-
- std::weak_ptr<WsConnectionObserver> observer_;
-
- // each element contains the data to be sent and whether it's binary or not
- std::deque<WsBuffer> write_queue_;
- std::mutex write_queue_mutex_;
- // The connection object should not outlive the context object. This reference
- // guarantees it.
- std::shared_ptr<WsConnectionContextImpl> context_;
-};
-
-class WsConnectionContextImpl
- : public WsConnectionContext,
- public std::enable_shared_from_this<WsConnectionContextImpl> {
- public:
- WsConnectionContextImpl(struct lws_context* lws_ctx);
- ~WsConnectionContextImpl() override;
-
- std::shared_ptr<WsConnection> CreateConnection(
- int port, const std::string& addr, const std::string& path,
- WsConnection::Security secure,
- std::weak_ptr<WsConnectionObserver> observer,
- const std::vector<std::pair<std::string, std::string>>& headers) override;
-
- void RememberConnection(void*, std::weak_ptr<WsConnectionImpl>);
- void ForgetConnection(void*);
- std::shared_ptr<WsConnectionImpl> GetConnection(void*);
-
- struct lws_context* lws_context() {
- return lws_context_;
- }
-
- private:
- void Start();
-
- std::map<void*, std::weak_ptr<WsConnectionImpl>> weak_by_ptr_;
- std::mutex map_mutex_;
- struct lws_context* lws_context_;
- std::thread message_loop_;
-};
-
-int LwsCallback(struct lws* wsi, enum lws_callback_reasons reason, void* user,
- void* in, size_t len);
-void CreateConnectionCallback(lws_sorted_usec_list_t* sul);
-
-namespace {
-
-constexpr char kProtocolName[] = "cf-webrtc-device";
-constexpr int kBufferSize = 65536;
-
-const uint32_t backoff_ms[] = {1000, 2000, 3000, 4000, 5000};
-
-const lws_retry_bo_t kRetry = {
- .retry_ms_table = backoff_ms,
- .retry_ms_table_count = LWS_ARRAY_SIZE(backoff_ms),
- .conceal_count = LWS_ARRAY_SIZE(backoff_ms),
-
- .secs_since_valid_ping = 3, /* force PINGs after secs idle */
- .secs_since_valid_hangup = 10, /* hangup after secs idle */
-
- .jitter_percent = 20,
-};
-
-const struct lws_protocols kProtocols[2] = {
- {kProtocolName, LwsCallback, 0, kBufferSize, 0, NULL, 0},
- {NULL, NULL, 0, 0, 0, NULL, 0}};
-
-} // namespace
-
-std::shared_ptr<WsConnectionContext> WsConnectionContext::Create() {
- struct lws_context_creation_info context_info = {};
- context_info.port = CONTEXT_PORT_NO_LISTEN;
- context_info.options = LWS_SERVER_OPTION_DO_SSL_GLOBAL_INIT;
- context_info.protocols = kProtocols;
- struct lws_context* lws_ctx = lws_create_context(&context_info);
- if (!lws_ctx) {
- return nullptr;
- }
- return std::shared_ptr<WsConnectionContext>(
- new WsConnectionContextImpl(lws_ctx));
-}
-
-WsConnectionContextImpl::WsConnectionContextImpl(struct lws_context* lws_ctx)
- : lws_context_(lws_ctx) {
- Start();
-}
-
-WsConnectionContextImpl::~WsConnectionContextImpl() {
- lws_context_destroy(lws_context_);
- if (message_loop_.joinable()) {
- message_loop_.join();
- }
-}
-
-void WsConnectionContextImpl::Start() {
- message_loop_ = std::thread([this]() {
- for (;;) {
- if (lws_service(lws_context_, 0) < 0) {
- break;
- }
- }
- });
-}
-
-std::shared_ptr<WsConnection> WsConnectionContextImpl::CreateConnection(
- int port, const std::string& addr, const std::string& path,
- WsConnection::Security security,
- std::weak_ptr<WsConnectionObserver> observer,
- const std::vector<std::pair<std::string, std::string>>& headers) {
- return std::shared_ptr<WsConnection>(new WsConnectionImpl(
- port, addr, path, security, headers, observer, shared_from_this()));
-}
-
-std::shared_ptr<WsConnectionImpl> WsConnectionContextImpl::GetConnection(
- void* raw) {
- std::shared_ptr<WsConnectionImpl> connection;
- {
- std::lock_guard<std::mutex> lock(map_mutex_);
- if (weak_by_ptr_.count(raw) == 0) {
- return nullptr;
- }
- connection = weak_by_ptr_[raw].lock();
- if (!connection) {
- weak_by_ptr_.erase(raw);
- }
- }
- return connection;
-}
-
-void WsConnectionContextImpl::RememberConnection(
- void* raw, std::weak_ptr<WsConnectionImpl> conn) {
- std::lock_guard<std::mutex> lock(map_mutex_);
- weak_by_ptr_.emplace(
- std::pair<void*, std::weak_ptr<WsConnectionImpl>>(raw, conn));
-}
-
-void WsConnectionContextImpl::ForgetConnection(void* raw) {
- std::lock_guard<std::mutex> lock(map_mutex_);
- weak_by_ptr_.erase(raw);
-}
-
-WsConnectionImpl::WsConnectionImpl(
- int port, const std::string& addr, const std::string& path,
- Security security,
- const std::vector<std::pair<std::string, std::string>>& headers,
- std::weak_ptr<WsConnectionObserver> observer,
- std::shared_ptr<WsConnectionContextImpl> context)
- : port_(port),
- addr_(addr),
- path_(path),
- security_(security),
- headers_(headers),
- observer_(observer),
- context_(context) {}
-
-WsConnectionImpl::~WsConnectionImpl() {
- context_->ForgetConnection(this);
- // This will cause the callback to be called which will drop the connection
- // after seeing the context doesn't remember this object
- lws_callback_on_writable(wsi_);
-}
-
-void WsConnectionImpl::Connect() {
- memset(&extended_sul_.sul, 0, sizeof(extended_sul_.sul));
- extended_sul_.weak_this = weak_from_this();
- lws_sul_schedule(context_->lws_context(), 0, &extended_sul_.sul,
- CreateConnectionCallback, 1);
-}
-
-void WsConnectionImpl::AddHttpHeaders(unsigned char** p,
- unsigned char* end) const {
- for (const auto& header_entry: headers_) {
- const auto& name = header_entry.first;
- const auto& value = header_entry.second;
- auto res = lws_add_http_header_by_name(
- wsi_, reinterpret_cast<const unsigned char*>(name.c_str()),
- reinterpret_cast<const unsigned char*>(value.c_str()), value.size(), p,
- end);
- if (res != 0) {
- LOG(ERROR) << "Unable to add header: " << name;
- }
- }
- if (!headers_.empty()) {
- // Let LWS know we added some headers.
- lws_client_http_body_pending(wsi_, 1);
- }
-}
-
-void WsConnectionImpl::OnError(const std::string& error) {
- auto observer = observer_.lock();
- if (observer) {
- observer->OnError(error);
- }
-}
-void WsConnectionImpl::OnReceive(const uint8_t* data, size_t len,
- bool is_binary) {
- auto observer = observer_.lock();
- if (observer) {
- observer->OnReceive(data, len, is_binary);
- }
-}
-void WsConnectionImpl::OnOpen() {
- auto observer = observer_.lock();
- if (observer) {
- observer->OnOpen();
- }
-}
-void WsConnectionImpl::OnClose() {
- auto observer = observer_.lock();
- if (observer) {
- observer->OnClose();
- }
-}
-
-void WsConnectionImpl::OnWriteable() {
- WsBuffer buffer;
- {
- std::lock_guard<std::mutex> lock(write_queue_mutex_);
- if (write_queue_.size() == 0) {
- return;
- }
- buffer = std::move(write_queue_.front());
- write_queue_.pop_front();
- }
- auto flags = lws_write_ws_flags(
- buffer.is_binary() ? LWS_WRITE_BINARY : LWS_WRITE_TEXT, true, true);
- auto res = lws_write(wsi_, buffer.data(), buffer.size(),
- (enum lws_write_protocol)flags);
- if (res != buffer.size()) {
- LOG(WARNING) << "Unable to send the entire message!";
- }
-}
-
-bool WsConnectionImpl::Send(const uint8_t* data, size_t len, bool binary) {
- if (!wsi_) {
- LOG(WARNING) << "Send called on an uninitialized connection!!";
- return false;
- }
- WsBuffer buffer(data, len, binary);
- {
- std::lock_guard<std::mutex> lock(write_queue_mutex_);
- write_queue_.emplace_back(std::move(buffer));
- }
-
- lws_callback_on_writable(wsi_);
- return true;
-}
-
-int LwsCallback(struct lws* wsi, enum lws_callback_reasons reason, void* user,
- void* in, size_t len) {
- constexpr int DROP = -1;
- constexpr int OK = 0;
-
- // For some values of `reason`, `user` doesn't point to the value provided
- // when the connection was created. This function object should be used with
- // care.
- auto with_connection =
- [wsi, user](std::function<void(std::shared_ptr<WsConnectionImpl>)> cb) {
- auto context = reinterpret_cast<WsConnectionContextImpl*>(user);
- auto connection = context->GetConnection(wsi);
- if (!connection) {
- return DROP;
- }
- cb(connection);
- return OK;
- };
-
- switch (reason) {
- case LWS_CALLBACK_CLIENT_CONNECTION_ERROR:
- return with_connection(
- [in](std::shared_ptr<WsConnectionImpl> connection) {
- connection->OnError(in ? (char*)in : "(null)");
- });
-
- case LWS_CALLBACK_CLIENT_RECEIVE:
- return with_connection(
- [in, len, wsi](std::shared_ptr<WsConnectionImpl> connection) {
- connection->OnReceive((const uint8_t*)in, len,
- lws_frame_is_binary(wsi));
- });
-
- case LWS_CALLBACK_CLIENT_ESTABLISHED:
- return with_connection([](std::shared_ptr<WsConnectionImpl> connection) {
- connection->OnOpen();
- });
-
- case LWS_CALLBACK_CLIENT_CLOSED:
- return with_connection([](std::shared_ptr<WsConnectionImpl> connection) {
- connection->OnClose();
- });
-
- case LWS_CALLBACK_CLIENT_WRITEABLE:
- return with_connection([](std::shared_ptr<WsConnectionImpl> connection) {
- connection->OnWriteable();
- });
-
- case LWS_CALLBACK_CLIENT_APPEND_HANDSHAKE_HEADER:
- return with_connection(
- [in, len](std::shared_ptr<WsConnectionImpl> connection) {
- auto p = reinterpret_cast<unsigned char**>(in);
- auto end = (*p) + len;
- connection->AddHttpHeaders(p, end);
- });
-
- case LWS_CALLBACK_CLIENT_HTTP_WRITEABLE:
- // This callback is only called when we add additional HTTP headers, let
- // LWS know we're done modifying the HTTP request.
- lws_client_http_body_pending(wsi, 0);
- return 0;
-
- default:
- LOG(VERBOSE) << "Unhandled value: " << reason;
- return lws_callback_http_dummy(wsi, reason, user, in, len);
- }
-}
-
-void CreateConnectionCallback(lws_sorted_usec_list_t* sul) {
- std::shared_ptr<WsConnectionImpl> connection =
- reinterpret_cast<WsConnectionImpl::CreateConnectionSul*>(sul)
- ->weak_this.lock();
- if (!connection) {
- LOG(WARNING) << "The object was already destroyed by the time of the first "
- << "connection attempt. That's unusual.";
- return;
- }
- connection->ConnectInner();
-}
-
-void WsConnectionImpl::ConnectInner() {
- struct lws_client_connect_info connect_info;
-
- memset(&connect_info, 0, sizeof(connect_info));
-
- connect_info.context = context_->lws_context();
- connect_info.port = port_;
- connect_info.address = addr_.c_str();
- connect_info.path = path_.c_str();
- connect_info.host = connect_info.address;
- connect_info.origin = connect_info.address;
- switch (security_) {
- case Security::kAllowSelfSigned:
- connect_info.ssl_connection = LCCSCF_ALLOW_SELFSIGNED |
- LCCSCF_SKIP_SERVER_CERT_HOSTNAME_CHECK |
- LCCSCF_USE_SSL;
- break;
- case Security::kStrict:
- connect_info.ssl_connection = LCCSCF_USE_SSL;
- break;
- case Security::kInsecure:
- connect_info.ssl_connection = 0;
- break;
- }
- connect_info.protocol = "webrtc-operator";
- connect_info.local_protocol_name = kProtocolName;
- connect_info.pwsi = &wsi_;
- connect_info.retry_and_idle_policy = &kRetry;
- // There is no guarantee the connection object still exists when the callback
- // is called. Put the context instead as the user data which is guaranteed to
- // still exist and holds a weak ptr to the connection.
- connect_info.userdata = context_.get();
-
- if (lws_client_connect_via_info(&connect_info)) {
- // wsi_ is not initialized until after the call to
- // lws_client_connect_via_info(). Luckily, this is guaranteed to run before
- // the protocol callback is called because it runs in the same loop.
- context_->RememberConnection(wsi_, weak_from_this());
- } else {
- LOG(ERROR) << "Connection failed!";
- }
-}
diff --git a/host/frontend/webrtc/lib/ws_connection.h b/host/frontend/webrtc/lib/ws_connection.h
deleted file mode 100644
index 1c03265..0000000
--- a/host/frontend/webrtc/lib/ws_connection.h
+++ /dev/null
@@ -1,72 +0,0 @@
-//
-// Copyright (C) 2020 The Android Open Source Project
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-//
-
-#pragma once
-
-#include <string.h>
-
-#include <deque>
-#include <functional>
-#include <map>
-#include <memory>
-#include <mutex>
-#include <string>
-#include <thread>
-#include <vector>
-
-#include <libwebsockets.h>
-
-class WsConnectionObserver {
- public:
- virtual ~WsConnectionObserver() = default;
- virtual void OnOpen() = 0;
- virtual void OnClose() = 0;
- virtual void OnError(const std::string& error) = 0;
- virtual void OnReceive(const uint8_t* msg, size_t length, bool is_binary) = 0;
-};
-
-class WsConnection {
- public:
- enum class Security {
- kInsecure,
- kAllowSelfSigned,
- kStrict,
- };
-
- virtual ~WsConnection() = default;
-
- virtual void Connect() = 0;
-
- virtual bool Send(const uint8_t* data, size_t len, bool binary = false) = 0;
-
- protected:
- WsConnection() = default;
-};
-
-class WsConnectionContext {
- public:
- static std::shared_ptr<WsConnectionContext> Create();
-
- virtual ~WsConnectionContext() = default;
-
- virtual std::shared_ptr<WsConnection> CreateConnection(
- int port, const std::string& addr, const std::string& path,
- WsConnection::Security secure,
- std::weak_ptr<WsConnectionObserver> observer,
- const std::vector<std::pair<std::string, std::string>>& headers = {}) = 0;
-
- protected:
- WsConnectionContext() = default;
-};
diff --git a/host/frontend/webrtc/main.cpp b/host/frontend/webrtc/main.cpp
index 57d5644..b607dca 100644
--- a/host/frontend/webrtc/main.cpp
+++ b/host/frontend/webrtc/main.cpp
@@ -30,6 +30,7 @@
#include "common/libs/fs/shared_fd.h"
#include "common/libs/utils/files.h"
#include "host/frontend/webrtc/audio_handler.h"
+#include "host/frontend/webrtc/client_server.h"
#include "host/frontend/webrtc/connection_observer.h"
#include "host/frontend/webrtc/display_handler.h"
#include "host/frontend/webrtc/kernel_log_events_handler.h"
@@ -59,6 +60,7 @@
"Whether to send input events in virtio format.");
DEFINE_int32(audio_server_fd, -1, "An fd to listen on for audio frames");
DEFINE_int32(camera_streamer_fd, -1, "An fd to send client camera frames");
+DEFINE_string(client_dir, "webrtc", "Location of the client files");
using cuttlefish::AudioHandler;
using cuttlefish::CfConnectionObserverFactory;
@@ -68,6 +70,7 @@
using cuttlefish::webrtc_streaming::Streamer;
using cuttlefish::webrtc_streaming::StreamerConfig;
using cuttlefish::webrtc_streaming::VideoSink;
+using cuttlefish::webrtc_streaming::ServerConfig;
class CfOperatorObserver
: public cuttlefish::webrtc_streaming::OperatorObserver {
@@ -131,6 +134,12 @@
return std::make_unique<cuttlefish::AudioServer>(audio_server_fd);
}
+fruit::Component<cuttlefish::CustomActionConfigProvider> WebRtcComponent() {
+ return fruit::createComponent()
+ .install(cuttlefish::ConfigFlagPlaceholder)
+ .install(cuttlefish::CustomActionsComponent);
+};
+
int main(int argc, char** argv) {
cuttlefish::DefaultSubprocessLogging(argv);
::gflags::ParseCommandLineFlags(&argc, &argv, true);
@@ -197,6 +206,8 @@
auto screen_connector_ptr = cuttlefish::DisplayHandler::ScreenConnector::Get(
FLAGS_frame_server_fd, host_mode_ctrl);
auto& screen_connector = *(screen_connector_ptr.get());
+ auto client_server = cuttlefish::ClientFilesServer::New(FLAGS_client_dir);
+ CHECK(client_server) << "Failed to initialize client files server";
// create confirmation UI service, giving host_mode_ctrl and
// screen_connector
@@ -207,15 +218,21 @@
StreamerConfig streamer_config;
streamer_config.device_id = instance.webrtc_device_id();
+ streamer_config.client_files_port = client_server->port();
streamer_config.tcp_port_range = cvd_config->webrtc_tcp_port_range();
streamer_config.udp_port_range = cvd_config->webrtc_udp_port_range();
streamer_config.operator_server.addr = cvd_config->sig_server_address();
streamer_config.operator_server.port = cvd_config->sig_server_port();
streamer_config.operator_server.path = cvd_config->sig_server_path();
- streamer_config.operator_server.security =
- cvd_config->sig_server_strict()
- ? WsConnection::Security::kStrict
- : WsConnection::Security::kAllowSelfSigned;
+ if (cvd_config->sig_server_secure()) {
+ streamer_config.operator_server.security =
+ cvd_config->sig_server_strict()
+ ? ServerConfig::Security::kStrict
+ : ServerConfig::Security::kAllowSelfSigned;
+ } else {
+ streamer_config.operator_server.security =
+ ServerConfig::Security::kInsecure;
+ }
if (!cvd_config->sig_server_headers_path().empty()) {
streamer_config.operator_server.http_headers =
@@ -265,7 +282,6 @@
CHECK(local_recorder) << "Could not create local recorder";
streamer->RecordDisplays(*local_recorder);
- display_handler->IncClientCount();
}
observer_factory->SetDisplayHandler(display_handler);
@@ -310,7 +326,17 @@
action_server_fds[server] = fd;
}
- for (const auto& custom_action : cvd_config->custom_actions()) {
+ fruit::Injector<cuttlefish::CustomActionConfigProvider> injector(
+ WebRtcComponent);
+ for (auto& fragment :
+ injector.getMultibindings<cuttlefish::ConfigFragment>()) {
+ CHECK(cvd_config->LoadFragment(*fragment))
+ << "Failed to load config fragment";
+ }
+
+ const auto& actions_provider =
+ injector.get<cuttlefish::CustomActionConfigProvider&>();
+ for (const auto& custom_action : actions_provider.CustomActions()) {
if (custom_action.shell_command) {
if (custom_action.buttons.size() != 1) {
LOG(FATAL) << "Expected exactly one button for custom action command: "
diff --git a/host/frontend/webrtc_operator/Android.bp b/host/frontend/webrtc_operator/Android.bp
index 54eda44..c734ae0 100644
--- a/host/frontend/webrtc_operator/Android.bp
+++ b/host/frontend/webrtc_operator/Android.bp
@@ -38,6 +38,7 @@
"webrtc_signaling_headers",
],
shared_libs: [
+ "libext2_blkid",
"libbase",
"liblog",
"libcrypto",
@@ -67,51 +68,23 @@
}
prebuilt_usr_share_host {
- name: "webrtc_style.css",
- src: "assets/style.css",
- filename: "style.css",
+ name: "webrtc_index.css",
+ src: "assets/index.css",
+ filename: "index.css",
sub_dir: "webrtc/assets",
}
prebuilt_usr_share_host {
- name: "webrtc_controls.css",
- src: "assets/controls.css",
- filename: "controls.css",
- sub_dir: "webrtc/assets",
-}
-
-prebuilt_usr_share_host {
- name: "webrtc_adb.js",
- src: "assets/js/adb.js",
- filename: "adb.js",
+ name: "webrtc_index.js",
+ src: "assets/js/index.js",
+ filename: "index.js",
sub_dir: "webrtc/assets/js",
}
prebuilt_usr_share_host {
- name: "webrtc_cf.js",
- src: "assets/js/cf_webrtc.js",
- filename: "cf_webrtc.js",
- sub_dir: "webrtc/assets/js",
-}
-
-prebuilt_usr_share_host {
- name: "webrtc_app.js",
- src: "assets/js/app.js",
- filename: "app.js",
- sub_dir: "webrtc/assets/js",
-}
-
-prebuilt_usr_share_host {
- name: "webrtc_controls.js",
- src: "assets/js/controls.js",
- filename: "controls.js",
- sub_dir: "webrtc/assets/js",
-}
-
-prebuilt_usr_share_host {
- name: "webrtc_rootcanal.js",
- src: "assets/js/rootcanal.js",
- filename: "rootcanal.js",
+ name: "webrtc_server_connector.js",
+ src: "assets/js/server_connector.js",
+ filename: "server_connector.js",
sub_dir: "webrtc/assets/js",
}
diff --git a/common/libs/utils/size_utils.cpp b/host/frontend/webrtc_operator/assets/index.css
similarity index 64%
copy from common/libs/utils/size_utils.cpp
copy to host/frontend/webrtc_operator/assets/index.css
index 9f25445..335e075 100644
--- a/common/libs/utils/size_utils.cpp
+++ b/host/frontend/webrtc_operator/assets/index.css
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2018 The Android Open Source Project
+ * Copyright (C) 2019 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -14,15 +14,24 @@
* limitations under the License.
*/
-#include "common/libs/utils/size_utils.h"
-
-#include <unistd.h>
-
-namespace cuttlefish {
-
-uint64_t AlignToPowerOf2(uint64_t val, uint8_t align_log) {
- uint64_t align = 1ULL << align_log;
- return ((val + (align - 1)) / align) * align;
+body {
+ background-color:black;
+ margin: 0;
}
-} // namespace cuttlefish
+#device-selector {
+ color: whitesmoke;
+}
+
+#device-selector li.device-entry {
+ cursor: pointer;
+}
+
+#refresh-list {
+ cursor: pointer;
+}
+
+#device-list .device-entry button {
+ margin-left: 10px;
+}
+
diff --git a/host/frontend/webrtc_operator/assets/index.html b/host/frontend/webrtc_operator/assets/index.html
index f0dc25e..09d8c70 100644
--- a/host/frontend/webrtc_operator/assets/index.html
+++ b/host/frontend/webrtc_operator/assets/index.html
@@ -18,11 +18,8 @@
<head>
<title>My Virtual Device Playground</title>
- <link rel="stylesheet" type="text/css" href="style.css" >
- <link rel="stylesheet" type="text/css" href="controls.css" >
- <link rel="stylesheet" href="https://fonts.googleapis.com/icon?family=Material+Icons+Outlined">
+ <link rel="stylesheet" type="text/css" href="index.css" >
<link rel="stylesheet" href="https://fonts.googleapis.com/icon?family=Material+Icons">
- <script src="https://ajax.googleapis.com/ajax/libs/jquery/3.6.0/jquery.min.js"></script>
</head>
<body>
@@ -30,57 +27,6 @@
<h1>Available devices <span id='refresh-list'>↻</span></h1>
<ul id="device-list"></ul>
</section>
- <section id='device-connection'>
- <div id='header'>
- <div id='app-controls'>
- <div id="keyboard-capture-control" title="Capture Keyboard"></div>
- <div id="mic-capture-control" title="Capture Microphone"></div>
- <div id="camera-control" title="Capture Camera"></div>
- <audio autoplay controls id="device-audio"></audio>
- </div>
- <div id='status-div'>
- <h3 id='status-message' class='connecting'>Connecting to device</h3>
- </div>
- </div>
- <div id='controls-and-displays'>
- <div id='control-panel-default-buttons' class='control-panel-column'>
- <button id='device-details-button' title='Device Details' class='material-icons'>
- settings
- </button>
- <button id='bluetooth-console-button' title='Bluetooth console' class='material-icons'>
- settings_bluetooth
- </button>
- </div>
- <div id='control-panel-custom-buttons' class='control-panel-column'></div>
- <div id='device-displays'>
- </div>
- </div>
- </section>
- <div id='device-details-modal' class='modal'>
- <div id='device-details-modal-header' class='modal-header'>
- <h2>Device Details</h2>
- <button id='device-details-close' title='Close' class='material-icons modal-close'>close</button>
- </div>
- <hr>
- <h3>Hardware Configuration</h3>
- <span id='device-details-hardware'>unknown</span>
- </div>
- <div id='bluetooth-console-modal' class='modal'>
- <div id='bluetooth-console-modal-header' class='modal-header'>
- <h2>Bluetooth Console</h2>
- <button id='bluetooth-console-close' title='Close' class='material-icons modal-close'>close</button>
- </div>
- <div>
- <table>
- <tr><td colspan='2'><textarea id='bluetooth-console-view' readonly rows='10' cols='60'></textarea></td></tr>
- <tr><td width='1'><p id='bluetooth-console-cmd-label'>Command:</p></td><td width='100'><input id='bluetooth-console-input' type='text'></input></td></tr>
- </table>
- </div>
- </div>
- <script src="js/adb.js"></script>
- <script src="js/rootcanal.js"></script>
- <script src="js/cf_webrtc.js" type="module"></script>
- <script src="js/controls.js"></script>
- <script src="js/app.js"></script>
+ <script src="js/index.js"></script>
</body>
</html>
diff --git a/host/frontend/webrtc_operator/assets/js/adb.js b/host/frontend/webrtc_operator/assets/js/adb.js
deleted file mode 100644
index 75540ae..0000000
--- a/host/frontend/webrtc_operator/assets/js/adb.js
+++ /dev/null
@@ -1,204 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-let adb_ws;
-
-let utf8Encoder = new TextEncoder();
-let utf8Decoder = new TextDecoder();
-
-const A_CNXN = 0x4e584e43;
-const A_OPEN = 0x4e45504f;
-const A_WRTE = 0x45545257;
-const A_OKAY = 0x59414b4f;
-
-const kLocalChannelId = 666;
-
-let array = new Uint8Array();
-
-function setU32LE(array, offset, x) {
- array[offset] = x & 0xff;
- array[offset + 1] = (x >> 8) & 0xff;
- array[offset + 2] = (x >> 16) & 0xff;
- array[offset + 3] = x >> 24;
-}
-
-function getU32LE(array, offset) {
- let x = array[offset]
- | (array[offset + 1] << 8)
- | (array[offset + 2] << 16)
- | (array[offset + 3] << 24);
-
- return x >>> 0; // convert signed to unsigned if necessary.
-}
-
-function computeChecksum(array) {
- let sum = 0;
- let i;
- for (i = 0; i < array.length; ++i) {
- sum = ((sum + array[i]) & 0xffffffff) >>> 0;
- }
-
- return sum;
-}
-
-function createAdbMessage(command, arg0, arg1, payload) {
- let arrayBuffer = new ArrayBuffer(24 + payload.length);
- let array = new Uint8Array(arrayBuffer);
- setU32LE(array, 0, command);
- setU32LE(array, 4, arg0);
- setU32LE(array, 8, arg1);
- setU32LE(array, 12, payload.length);
- setU32LE(array, 16, computeChecksum(payload));
- setU32LE(array, 20, command ^ 0xffffffff);
- array.set(payload, 24);
-
- return arrayBuffer;
-}
-
-function adbOpenConnection() {
- let systemIdentity = utf8Encoder.encode("Cray_II:1234:whatever");
-
- let arrayBuffer = createAdbMessage(
- A_CNXN, 0x1000000, 256 * 1024, systemIdentity);
-
- adb_ws.send(arrayBuffer);
-}
-
-function adbShell(command) {
- let destination = utf8Encoder.encode("shell:" + command);
-
- let arrayBuffer = createAdbMessage(A_OPEN, kLocalChannelId, 0, destination);
- adb_ws.send(arrayBuffer);
- awaitConnection();
-}
-
-function adbSendOkay(remoteId) {
- let payload = new Uint8Array(0);
-
- let arrayBuffer = createAdbMessage(
- A_OKAY, kLocalChannelId, remoteId, payload);
-
- adb_ws.send(arrayBuffer);
-}
-
-function JoinArrays(arr1, arr2) {
- let arr = new Uint8Array(arr1.length + arr2.length);
- arr.set(arr1, 0);
- arr.set(arr2, arr1.length);
- return arr;
-}
-
-// Simple lifecycle management that executes callbacks based on connection state.
-//
-// Any attempt to initiate a command (e.g. creating a connection, sending a message)
-// (re)starts a timer. Any response back from any command stops that timer.
-const timeoutMs = 3000;
-let connectedCb;
-let disconnectedCb;
-let disconnectedTimeout;
-function awaitConnection() {
- clearTimeout(disconnectedTimeout);
- if (disconnectedCb) {
- disconnectedTimeout = setTimeout(disconnectedCb, timeoutMs);
- }
-}
-function connected() {
- if (disconnectedTimeout) {
- clearTimeout(disconnectedTimeout);
- }
- if (connectedCb) {
- connectedCb();
- }
-}
-
-function adbOnMessage(arrayBuffer) {
- // console.log("adb_ws: onmessage (" + arrayBuffer.byteLength + " bytes)");
- array = JoinArrays(array, new Uint8Array(arrayBuffer));
-
- while (array.length > 0) {
- if (array.length < 24) {
- // Incomplete package, must wait for more data.
- return;
- }
-
- let command = getU32LE(array, 0);
- let magic = getU32LE(array, 20);
-
- if (command != ((magic ^ 0xffffffff) >>> 0)) {
- console.log("command = " + command + ", magic = " + magic);
- console.log("adb message command vs magic failed.");
- return;
- }
-
- let payloadLength = getU32LE(array, 12);
-
- if (array.length < 24 + payloadLength) {
- // Incomplete package, must wait for more data.
- return;
- }
-
- let payloadChecksum = getU32LE(array, 16);
- let checksum = computeChecksum(array.slice(24));
-
- if (payloadChecksum != checksum) {
- console.log("adb message checksum mismatch.");
- // This can happen if a shell command executes while another
- // channel is receiving data.
- }
-
- switch (command) {
- case A_CNXN:
- {
- console.log("WebRTC adb connected.");
- connected();
- break;
- }
-
- case A_OKAY:
- {
- let remoteId = getU32LE(array, 4);
- console.log("WebRTC adb channel created w/ remoteId " + remoteId);
- connected();
- break;
- }
-
- case A_WRTE:
- {
- let remoteId = getU32LE(array, 4);
- adbSendOkay(remoteId);
- break;
- }
- }
- array = array.subarray(24 + payloadLength, array.length);
- }
-}
-
-function init_adb(devConn, ccb = connectedCb, dcb = disconnectedCb) {
- if (adb_ws) return;
-
- adb_ws = {
- send: function(buffer) {
- devConn.sendAdbMessage(buffer);
- }
- };
- connectedCb = ccb;
- disconnectedCb = dcb;
- awaitConnection();
-
- devConn.onAdbMessage(msg => adbOnMessage(msg));
-
- adbOpenConnection();
-}
diff --git a/host/frontend/webrtc_operator/assets/js/app.js b/host/frontend/webrtc_operator/assets/js/app.js
deleted file mode 100644
index db9c8a7..0000000
--- a/host/frontend/webrtc_operator/assets/js/app.js
+++ /dev/null
@@ -1,972 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-'use strict';
-
-function ConnectToDevice(device_id) {
- console.log('ConnectToDevice ', device_id);
- const keyboardCaptureCtrl = document.getElementById('keyboard-capture-control');
- createToggleControl(keyboardCaptureCtrl, "keyboard", onKeyboardCaptureToggle);
- const micCaptureCtrl = document.getElementById('mic-capture-control');
- createToggleControl(micCaptureCtrl, "mic", onMicCaptureToggle);
- const cameraCtrl = document.getElementById('camera-control');
- createToggleControl(cameraCtrl, "videocam", onVideoCaptureToggle);
-
- const deviceAudio = document.getElementById('device-audio');
- const deviceDisplays = document.getElementById('device-displays');
- const statusMessage = document.getElementById('status-message');
-
- let connectionAttemptDuration = 0;
- const intervalMs = 500;
- let deviceStatusEllipsisCount = 0;
- let animateDeviceStatusMessage = setInterval(function() {
- connectionAttemptDuration += intervalMs;
- if (connectionAttemptDuration > 30000) {
- statusMessage.className = 'error';
- statusMessage.textContent = 'Connection should have occurred by now. ' +
- 'Please attempt to restart the guest device.';
- } else {
- if (connectionAttemptDuration > 15000) {
- statusMessage.textContent = 'Connection is taking longer than expected';
- } else {
- statusMessage.textContent = 'Connecting to device';
- }
- deviceStatusEllipsisCount = (deviceStatusEllipsisCount + 1) % 4;
- statusMessage.textContent += '.'.repeat(deviceStatusEllipsisCount);
- }
- }, intervalMs);
-
- let buttons = {};
- let mouseIsDown = false;
- let deviceConnection;
- let touchIdSlotMap = new Map();
- let touchSlots = new Array();
- let deviceStateLidSwitchOpen = null;
- let deviceStateHingeAngleValue = null;
-
- function showAdbConnected() {
- // Screen changed messages are not reported until after boot has completed.
- // Certain default adb buttons change screen state, so wait for boot
- // completion before enabling these buttons.
- statusMessage.className = 'connected';
- statusMessage.textContent =
- 'adb connection established successfully.';
- setTimeout(function() {
- statusMessage.style.visibility = 'hidden';
- }, 5000);
- for (const [_, button] of Object.entries(buttons)) {
- if (button.adb) {
- button.button.disabled = false;
- }
- }
- }
-
- function initializeAdb() {
- init_adb(
- deviceConnection,
- showAdbConnected,
- function() {
- statusMessage.className = 'error';
- statusMessage.textContent = 'adb connection failed.';
- statusMessage.style.visibility = 'visible';
- for (const [_, button] of Object.entries(buttons)) {
- if (button.adb) {
- button.button.disabled = true;
- }
- }
- });
- }
-
- let currentRotation = 0;
- let currentDisplayDescriptions;
- function onControlMessage(message) {
- let message_data = JSON.parse(message.data);
- console.log(message_data)
- let metadata = message_data.metadata;
- if (message_data.event == 'VIRTUAL_DEVICE_BOOT_STARTED') {
- // Start the adb connection after receiving the BOOT_STARTED message.
- // (This is after the adbd start message. Attempting to connect
- // immediately after adbd starts causes issues.)
- initializeAdb();
- }
- if (message_data.event == 'VIRTUAL_DEVICE_SCREEN_CHANGED') {
- if (metadata.rotation != currentRotation) {
- // Animate the screen rotation.
- const targetRotation = metadata.rotation == 0 ? 0 : -90;
-
- $(deviceDisplays).animate(
- {
- textIndent: targetRotation,
- },
- {
- duration: 1000,
- step: function(now, tween) {
- resizeDeviceDisplays();
- },
- }
- );
- }
-
- currentRotation = metadata.rotation;
- }
- if (message_data.event == 'VIRTUAL_DEVICE_CAPTURE_IMAGE') {
- if (deviceConnection.cameraEnabled) {
- takePhoto();
- }
- }
- if (message_data.event == 'VIRTUAL_DEVICE_DISPLAY_POWER_MODE_CHANGED') {
- updateDisplayVisibility(metadata.display, metadata.mode);
- }
- }
-
- function updateDisplayVisibility(displayId, powerMode) {
- const display = document.getElementById('display_' + displayId).parentElement;
- if (display == null) {
- console.error('Unknown display id: ' + displayId);
- return;
- }
- switch (powerMode) {
- case 'On':
- display.style.visibility = 'visible';
- break;
- case 'Off':
- display.style.visibility = 'hidden';
- break;
- default:
- console.error('Display ' + displayId + ' has unknown display power mode: ' + powerMode);
- }
- }
-
- function getTransformRotation(element) {
- if (!element.style.textIndent) {
- return 0;
- }
- // Remove 'px' and convert to float.
- return parseFloat(element.style.textIndent.slice(0, -2));
- }
-
- let anyDeviceDisplayLoaded = false;
- function onDeviceDisplayLoaded() {
- if (anyDeviceDisplayLoaded) {
- return;
- }
- anyDeviceDisplayLoaded = true;
-
- clearInterval(animateDeviceStatusMessage);
- statusMessage.textContent = 'Awaiting bootup and adb connection. Please wait...';
- resizeDeviceDisplays();
-
- let deviceDisplayList =
- document.getElementsByClassName("device-display");
- for (const deviceDisplay of deviceDisplayList) {
- deviceDisplay.style.visibility = 'visible';
- }
-
- // Enable the buttons after the screen is visible.
- for (const [_, button] of Object.entries(buttons)) {
- if (!button.adb) {
- button.button.disabled = false;
- }
- }
- // Start the adb connection if it is not already started.
- initializeAdb();
- }
-
- // Creates a <video> element and a <div> container element for each display.
- // The extra <div> container elements are used to maintain the width and
- // height of the device as the CSS 'transform' property used on the <video>
- // element for rotating the device only affects the visuals of the element
- // and not its layout.
- function createDeviceDisplays(devConn) {
- for (const deviceDisplayDescription of currentDisplayDescriptions) {
- let deviceDisplay = document.createElement("div");
- deviceDisplay.classList.add("device-display");
- // Start the screen as hidden. Only show when data is ready.
- deviceDisplay.style.visibility = 'hidden';
-
- let deviceDisplayInfo = document.createElement("div");
- deviceDisplayInfo.classList.add("device-display-info");
- deviceDisplayInfo.id = deviceDisplayDescription.stream_id + '_info';
- deviceDisplay.appendChild(deviceDisplayInfo);
-
- let deviceDisplayVideo = document.createElement("video");
- deviceDisplayVideo.autoplay = true;
- deviceDisplayVideo.id = deviceDisplayDescription.stream_id;
- deviceDisplayVideo.classList.add("device-display-video");
- deviceDisplayVideo.addEventListener('loadeddata', (evt) => {
- onDeviceDisplayLoaded();
- });
- deviceDisplay.appendChild(deviceDisplayVideo);
-
- deviceDisplays.appendChild(deviceDisplay);
-
- let stream_id = deviceDisplayDescription.stream_id;
- devConn.getStream(stream_id).then(stream => {
- deviceDisplayVideo.srcObject = stream;
- }).catch(e => console.error('Unable to get display stream: ', e));
- }
- }
-
- function takePhoto() {
- const imageCapture = deviceConnection.imageCapture;
- if (imageCapture) {
- const photoSettings = {
- imageWidth: deviceConnection.cameraWidth,
- imageHeight: deviceConnection.cameraHeight
- }
- imageCapture.takePhoto(photoSettings)
- .then(blob => blob.arrayBuffer())
- .then(buffer => deviceConnection.sendOrQueueCameraData(buffer))
- .catch(error => console.log(error));
- }
- }
-
- function resizeDeviceDisplays() {
- // Padding between displays.
- const deviceDisplayWidthPadding = 10;
- // Padding for the display info above each display video.
- const deviceDisplayHeightPadding = 38;
-
- let deviceDisplayList =
- document.getElementsByClassName("device-display");
- let deviceDisplayVideoList =
- document.getElementsByClassName("device-display-video");
- let deviceDisplayInfoList =
- document.getElementsByClassName("device-display-info");
-
- const rotationDegrees = getTransformRotation(deviceDisplays);
- const rotationRadians = rotationDegrees * Math.PI / 180;
-
- // Auto-scale the screen based on window size.
- let availableWidth = deviceDisplays.clientWidth;
- let availableHeight = deviceDisplays.clientHeight - deviceDisplayHeightPadding;
-
- // Reserve space for padding between the displays.
- availableWidth = availableWidth -
- (currentDisplayDescriptions.length * deviceDisplayWidthPadding);
-
- // Loop once over all of the displays to compute the total space needed.
- let neededWidth = 0;
- let neededHeight = 0;
- for (let i = 0; i < deviceDisplayList.length; i++) {
- let deviceDisplayDescription = currentDisplayDescriptions[i];
- let deviceDisplayVideo = deviceDisplayVideoList[i];
-
- const originalDisplayWidth = deviceDisplayDescription.x_res;
- const originalDisplayHeight = deviceDisplayDescription.y_res;
-
- const neededBoundingBoxWidth =
- Math.abs(Math.cos(rotationRadians) * originalDisplayWidth) +
- Math.abs(Math.sin(rotationRadians) * originalDisplayHeight);
- const neededBoundingBoxHeight =
- Math.abs(Math.sin(rotationRadians) * originalDisplayWidth) +
- Math.abs(Math.cos(rotationRadians) * originalDisplayHeight);
-
- neededWidth = neededWidth + neededBoundingBoxWidth;
- neededHeight = Math.max(neededHeight, neededBoundingBoxHeight);
- }
-
- const scaling = Math.min(availableWidth / neededWidth,
- availableHeight / neededHeight);
-
- // Loop again over all of the displays to set the sizes and positions.
- let deviceDisplayLeftOffset = 0;
- for (let i = 0; i < deviceDisplayList.length; i++) {
- let deviceDisplay = deviceDisplayList[i];
- let deviceDisplayVideo = deviceDisplayVideoList[i];
- let deviceDisplayInfo = deviceDisplayInfoList[i];
- let deviceDisplayDescription = currentDisplayDescriptions[i];
-
- let rotated = currentRotation == 1 ? ' (Rotated)' : '';
- deviceDisplayInfo.textContent = `Display ${i} - ` +
- `${deviceDisplayDescription.x_res}x` +
- `${deviceDisplayDescription.y_res} ` +
- `(${deviceDisplayDescription.dpi} DPI)${rotated}`;
-
- const originalDisplayWidth = deviceDisplayDescription.x_res;
- const originalDisplayHeight = deviceDisplayDescription.y_res;
-
- const scaledDisplayWidth = originalDisplayWidth * scaling;
- const scaledDisplayHeight = originalDisplayHeight * scaling;
-
- const neededBoundingBoxWidth =
- Math.abs(Math.cos(rotationRadians) * originalDisplayWidth) +
- Math.abs(Math.sin(rotationRadians) * originalDisplayHeight);
- const neededBoundingBoxHeight =
- Math.abs(Math.sin(rotationRadians) * originalDisplayWidth) +
- Math.abs(Math.cos(rotationRadians) * originalDisplayHeight);
-
- const scaledBoundingBoxWidth = neededBoundingBoxWidth * scaling;
- const scaledBoundingBoxHeight = neededBoundingBoxHeight * scaling;
-
- const offsetX = (scaledBoundingBoxWidth - scaledDisplayWidth) / 2;
- const offsetY = (scaledBoundingBoxHeight - scaledDisplayHeight) / 2;
-
- deviceDisplayVideo.style.width = scaledDisplayWidth;
- deviceDisplayVideo.style.height = scaledDisplayHeight;
- deviceDisplayVideo.style.transform =
- `translateX(${offsetX}px) ` +
- `translateY(${offsetY}px) ` +
- `rotateZ(${rotationDegrees}deg) `;
-
- deviceDisplay.style.left = `${deviceDisplayLeftOffset}px`;
- deviceDisplay.style.width = scaledBoundingBoxWidth;
- deviceDisplay.style.height = scaledBoundingBoxHeight;
-
- deviceDisplayLeftOffset =
- deviceDisplayLeftOffset +
- deviceDisplayWidthPadding +
- scaledBoundingBoxWidth;
- }
- }
- window.onresize = resizeDeviceDisplays;
-
- function createControlPanelButton(command, title, icon_name,
- listener=onControlPanelButton,
- parent_id='control-panel-default-buttons') {
- let button = document.createElement('button');
- document.getElementById(parent_id).appendChild(button);
- button.title = title;
- button.dataset.command = command;
- button.disabled = true;
- // Capture mousedown/up/out commands instead of click to enable
- // hold detection. mouseout is used to catch if the user moves the
- // mouse outside the button while holding down.
- button.addEventListener('mousedown', listener);
- button.addEventListener('mouseup', listener);
- button.addEventListener('mouseout', listener);
- // Set the button image using Material Design icons.
- // See http://google.github.io/material-design-icons
- // and https://material.io/resources/icons
- button.classList.add('material-icons');
- button.innerHTML = icon_name;
- buttons[command] = { 'button': button }
- return buttons[command];
- }
- createControlPanelButton('power', 'Power', 'power_settings_new');
- createControlPanelButton('home', 'Home', 'home');
- createControlPanelButton('menu', 'Menu', 'menu');
- createControlPanelButton('rotate', 'Rotate', 'screen_rotation', onRotateButton);
- buttons['rotate'].adb = true;
- createControlPanelButton('volumemute', 'Volume Mute', 'volume_mute');
- createControlPanelButton('volumedown', 'Volume Down', 'volume_down');
- createControlPanelButton('volumeup', 'Volume Up', 'volume_up');
-
- let modalOffsets = {}
- function createModalButton(button_id, modal_id, close_id) {
- const modalButton = document.getElementById(button_id);
- const modalDiv = document.getElementById(modal_id);
- const modalHeader = modalDiv.querySelector('.modal-header');
- const modalClose = document.getElementById(close_id);
-
- // Position the modal to the right of the show modal button.
- modalDiv.style.top = modalButton.offsetTop;
- modalDiv.style.left = modalButton.offsetWidth + 30;
-
- function showHideModal(show) {
- if (show) {
- modalButton.classList.add('modal-button-opened')
- modalDiv.style.display = 'block';
- } else {
- modalButton.classList.remove('modal-button-opened')
- modalDiv.style.display = 'none';
- }
- }
- // Allow the show modal button to toggle the modal,
- modalButton.addEventListener('click',
- evt => showHideModal(modalDiv.style.display != 'block'));
- // but the close button always closes.
- modalClose.addEventListener('click',
- evt => showHideModal(false));
-
- // Allow the modal to be dragged by the header.
- modalOffsets[modal_id] = {
- midDrag: false,
- mouseDownOffsetX: null,
- mouseDownOffsetY: null,
- }
- modalHeader.addEventListener('mousedown',
- evt => {
- modalOffsets[modal_id].midDrag = true;
- // Store the offset of the mouse location from the
- // modal's current location.
- modalOffsets[modal_id].mouseDownOffsetX =
- parseInt(modalDiv.style.left) - evt.clientX;
- modalOffsets[modal_id].mouseDownOffsetY =
- parseInt(modalDiv.style.top) - evt.clientY;
- });
- modalHeader.addEventListener('mousemove',
- evt => {
- let offsets = modalOffsets[modal_id];
- if (offsets.midDrag) {
- // Move the modal to the mouse location plus the
- // offset calculated on the initial mouse-down.
- modalDiv.style.left =
- evt.clientX + offsets.mouseDownOffsetX;
- modalDiv.style.top =
- evt.clientY + offsets.mouseDownOffsetY;
- }
- });
- document.addEventListener('mouseup',
- evt => {
- modalOffsets[modal_id].midDrag = false;
- });
- }
-
- createModalButton(
- 'device-details-button', 'device-details-modal', 'device-details-close');
- createModalButton(
- 'bluetooth-console-button', 'bluetooth-console-modal', 'bluetooth-console-close');
-
- let options = {
- wsUrl: ((location.protocol == 'http:') ? 'ws://' : 'wss://') +
- location.host + '/connect_client',
- };
-
- function showWebrtcError() {
- statusMessage.className = 'error';
- statusMessage.textContent = 'No connection to the guest device. ' +
- 'Please ensure the WebRTC process on the host machine is active.';
- statusMessage.style.visibility = 'visible';
- deviceDisplays.style.display = 'none';
- for (const [_, button] of Object.entries(buttons)) {
- button.button.disabled = true;
- }
- }
-
- import('./cf_webrtc.js')
- .then(webrtcModule => webrtcModule.Connect(device_id, options))
- .then(devConn => {
- deviceConnection = devConn;
-
- console.log(deviceConnection.description);
-
- currentDisplayDescriptions = devConn.description.displays;
-
- createDeviceDisplays(devConn);
- for (const audio_desc of devConn.description.audio_streams) {
- let stream_id = audio_desc.stream_id;
- devConn.getStream(stream_id).then(stream => {
- deviceAudio.srcObject = stream;
- }).catch(e => console.error('Unable to get audio stream: ', e));
- }
- startMouseTracking(); // TODO stopMouseTracking() when disconnected
- updateDeviceHardwareDetails(deviceConnection.description.hardware);
- if (deviceConnection.description.custom_control_panel_buttons.length > 0) {
- document.getElementById('control-panel-custom-buttons').style.display = 'flex';
- for (const button of deviceConnection.description.custom_control_panel_buttons) {
- if (button.shell_command) {
- // This button's command is handled by sending an ADB shell command.
- createControlPanelButton(button.command, button.title, button.icon_name,
- e => onCustomShellButton(button.shell_command, e),
- 'control-panel-custom-buttons');
- buttons[button.command].adb = true;
- } else if (button.device_states) {
- // This button corresponds to variable hardware device state(s).
- createControlPanelButton(button.command, button.title, button.icon_name,
- getCustomDeviceStateButtonCb(button.device_states),
- 'control-panel-custom-buttons');
- for (const device_state of button.device_states) {
- // hinge_angle is currently injected via an adb shell command that
- // triggers a guest binary.
- if ('hinge_angle_value' in device_state) {
- buttons[button.command].adb = true;
- }
- }
- } else {
- // This button's command is handled by custom action server.
- createControlPanelButton(button.command, button.title, button.icon_name,
- onControlPanelButton,
- 'control-panel-custom-buttons');
- }
- }
- }
- deviceConnection.onControlMessage(msg => onControlMessage(msg));
- // Show the error message and disable buttons when the WebRTC connection fails.
- deviceConnection.onConnectionStateChange(state => {
- if (state == 'disconnected' || state == 'failed') {
- showWebrtcError();
- }
- });
- deviceConnection.onBluetoothMessage(msg => {
- bluetoothConsole.addLine(decodeRootcanalMessage(msg));
- });
- }, rejection => {
- console.error('Unable to connect: ', rejection);
- showWebrtcError();
- });
-
- let hardwareDetailsText = '';
- let deviceStateDetailsText = '';
- function updateDeviceDetailsText() {
- document.getElementById('device-details-hardware').textContent = [
- hardwareDetailsText,
- deviceStateDetailsText,
- ].filter(e => e /*remove empty*/).join('\n');
- }
- function updateDeviceHardwareDetails(hardware) {
- let hardwareDetailsTextLines = [];
- Object.keys(hardware).forEach(function(key) {
- let value = hardware[key];
- hardwareDetailsTextLines.push(`${key} - ${value}`);
- });
-
- hardwareDetailsText = hardwareDetailsTextLines.join('\n');
- updateDeviceDetailsText();
- }
- function updateDeviceStateDetails() {
- let deviceStateDetailsTextLines = [];
- if (deviceStateLidSwitchOpen != null) {
- let state = deviceStateLidSwitchOpen ? 'Opened' : 'Closed';
- deviceStateDetailsTextLines.push(`Lid Switch - ${state}`);
- }
- if (deviceStateHingeAngleValue != null) {
- deviceStateDetailsTextLines.push(`Hinge Angle - ${deviceStateHingeAngleValue}`);
- }
- deviceStateDetailsText = deviceStateDetailsTextLines.join('\n');
- updateDeviceDetailsText();
- }
-
- function onKeyboardCaptureToggle(enabled) {
- if (enabled) {
- startKeyboardTracking();
- } else {
- stopKeyboardTracking();
- }
- }
-
- function onMicCaptureToggle(enabled) {
- deviceConnection.useMic(enabled);
- }
-
- function onVideoCaptureToggle(enabled) {
- deviceConnection.useVideo(enabled);
- }
-
- function cmdConsole(consoleViewName, consoleInputName) {
- let consoleView = document.getElementById(consoleViewName);
-
- let addString = function(str) {
- consoleView.value += str;
- consoleView.scrollTop = consoleView.scrollHeight;
- }
-
- let addLine = function(line) {
- addString(line + "\r\n");
- }
-
- let commandCallbacks = [];
-
- let addCommandListener = function(f) {
- commandCallbacks.push(f);
- }
-
- let onCommand = function(cmd) {
- cmd = cmd.trim();
-
- if (cmd.length == 0) return;
-
- commandCallbacks.forEach(f => {
- f(cmd);
- })
- }
-
- addCommandListener(cmd => addLine(">> " + cmd));
-
- let consoleInput = document.getElementById(consoleInputName);
-
- consoleInput.addEventListener('keydown', e => {
- if ((e.key && e.key == 'Enter') || e.keyCode == 13) {
- let command = e.target.value;
-
- e.target.value = '';
-
- onCommand(command);
- }
- })
-
- return {
- consoleView: consoleView,
- consoleInput: consoleInput,
- addLine: addLine,
- addString: addString,
- addCommandListener: addCommandListener,
- };
- }
-
- var bluetoothConsole = cmdConsole(
- 'bluetooth-console-view', 'bluetooth-console-input');
-
- bluetoothConsole.addCommandListener(cmd => {
- let inputArr = cmd.split(' ');
- let command = inputArr[0];
- inputArr.shift();
- let args = inputArr;
- deviceConnection.sendBluetoothMessage(createRootcanalMessage(command, args));
- })
-
- function onControlPanelButton(e) {
- if (e.type == 'mouseout' && e.which == 0) {
- // Ignore mouseout events if no mouse button is pressed.
- return;
- }
- deviceConnection.sendControlMessage(JSON.stringify({
- command: e.target.dataset.command,
- button_state: e.type == 'mousedown' ? "down" : "up",
- }));
- }
-
- function onRotateButton(e) {
- // Attempt to init adb again, in case the initial connection failed.
- // This succeeds immediately if already connected.
- initializeAdb();
- if (e.type == 'mousedown') {
- adbShell(
- '/vendor/bin/cuttlefish_sensor_injection rotate ' +
- (currentRotation == 0 ? 'landscape' : 'portrait'))
- }
- }
-
- function onCustomShellButton(shell_command, e) {
- // Attempt to init adb again, in case the initial connection failed.
- // This succeeds immediately if already connected.
- initializeAdb();
- if (e.type == 'mousedown') {
- adbShell(shell_command);
- }
- }
-
- function getCustomDeviceStateButtonCb(device_states) {
- let states = device_states;
- let index = 0;
- return e => {
- if (e.type == 'mousedown') {
- // Reset any overridden device state.
- adbShell('cmd device_state state reset');
- // Send a device_state message for the current state.
- let message = {
- command: 'device_state',
- ...states[index],
- };
- deviceConnection.sendControlMessage(JSON.stringify(message));
- console.log(JSON.stringify(message));
- if ('lid_switch_open' in states[index]) {
- deviceStateLidSwitchOpen = states[index].lid_switch_open;
- }
- if ('hinge_angle_value' in states[index]) {
- deviceStateHingeAngleValue = states[index].hinge_angle_value;
- // TODO(b/181157794): Use a custom Sensor HAL for hinge_angle injection
- // instead of this guest binary.
- adbShell(
- '/vendor/bin/cuttlefish_sensor_injection hinge_angle ' +
- states[index].hinge_angle_value);
- }
- // Update the Device Details view.
- updateDeviceStateDetails();
- // Cycle to the next state.
- index = (index + 1) % states.length;
- }
- }
- }
-
- function startMouseTracking() {
- let deviceDisplayList = document.getElementsByClassName("device-display");
- if (window.PointerEvent) {
- for (const deviceDisplay of deviceDisplayList) {
- deviceDisplay.addEventListener('pointerdown', onStartDrag);
- deviceDisplay.addEventListener('pointermove', onContinueDrag);
- deviceDisplay.addEventListener('pointerup', onEndDrag);
- }
- } else if (window.TouchEvent) {
- for (const deviceDisplay of deviceDisplayList) {
- deviceDisplay.addEventListener('touchstart', onStartDrag);
- deviceDisplay.addEventListener('touchmove', onContinueDrag);
- deviceDisplay.addEventListener('touchend', onEndDrag);
- }
- } else if (window.MouseEvent) {
- for (const deviceDisplay of deviceDisplayList) {
- deviceDisplay.addEventListener('mousedown', onStartDrag);
- deviceDisplay.addEventListener('mousemove', onContinueDrag);
- deviceDisplay.addEventListener('mouseup', onEndDrag);
- }
- }
- }
-
- function stopMouseTracking() {
- let deviceDisplayList = document.getElementsByClassName("device-display");
- if (window.PointerEvent) {
- for (const deviceDisplay of deviceDisplayList) {
- deviceDisplay.removeEventListener('pointerdown', onStartDrag);
- deviceDisplay.removeEventListener('pointermove', onContinueDrag);
- deviceDisplay.removeEventListener('pointerup', onEndDrag);
- }
- } else if (window.TouchEvent) {
- for (const deviceDisplay of deviceDisplayList) {
- deviceDisplay.removeEventListener('touchstart', onStartDrag);
- deviceDisplay.removeEventListener('touchmove', onContinueDrag);
- deviceDisplay.removeEventListener('touchend', onEndDrag);
- }
- } else if (window.MouseEvent) {
- for (const deviceDisplay of deviceDisplayList) {
- deviceDisplay.removeEventListener('mousedown', onStartDrag);
- deviceDisplay.removeEventListener('mousemove', onContinueDrag);
- deviceDisplay.removeEventListener('mouseup', onEndDrag);
- }
- }
- }
-
- function startKeyboardTracking() {
- document.addEventListener('keydown', onKeyEvent);
- document.addEventListener('keyup', onKeyEvent);
- }
-
- function stopKeyboardTracking() {
- document.removeEventListener('keydown', onKeyEvent);
- document.removeEventListener('keyup', onKeyEvent);
- }
-
- function onStartDrag(e) {
- e.preventDefault();
-
- // console.log("mousedown at " + e.pageX + " / " + e.pageY);
- mouseIsDown = true;
-
- sendEventUpdate(true, e);
- }
-
- function onEndDrag(e) {
- e.preventDefault();
-
- // console.log("mouseup at " + e.pageX + " / " + e.pageY);
- mouseIsDown = false;
-
- sendEventUpdate(false, e);
- }
-
- function onContinueDrag(e) {
- e.preventDefault();
-
- // console.log("mousemove at " + e.pageX + " / " + e.pageY + ", down=" +
- // mouseIsDown);
- if (mouseIsDown) {
- sendEventUpdate(true, e);
- }
- }
-
- function sendEventUpdate(down, e) {
- console.assert(deviceConnection, 'Can\'t send mouse update without device');
- var eventType = e.type.substring(0, 5);
-
- // The <video> element:
- const deviceDisplay = e.target;
-
- // Before the first video frame arrives there is no way to know width and
- // height of the device's screen, so turn every click into a click at 0x0.
- // A click at that position is not more dangerous than anywhere else since
- // the user is clicking blind anyways.
- const videoWidth = deviceDisplay.videoWidth? deviceDisplay.videoWidth: 1;
- const videoHeight = deviceDisplay.videoHeight? deviceDisplay.videoHeight: 1;
- const elementWidth = deviceDisplay.offsetWidth? deviceDisplay.offsetWidth: 1;
- const elementHeight = deviceDisplay.offsetHeight? deviceDisplay.offsetHeight: 1;
-
- // vh*ew > eh*vw? then scale h instead of w
- const scaleHeight = videoHeight * elementWidth > videoWidth * elementHeight;
- var elementScaling = 0, videoScaling = 0;
- if (scaleHeight) {
- elementScaling = elementHeight;
- videoScaling = videoHeight;
- } else {
- elementScaling = elementWidth;
- videoScaling = videoWidth;
- }
-
- // The screen uses the 'object-fit: cover' property in order to completely
- // fill the element while maintaining the screen content's aspect ratio.
- // Therefore:
- // - If vh*ew > eh*vw, w is scaled so that content width == element width
- // - Otherwise, h is scaled so that content height == element height
- const scaleWidth = videoHeight * elementWidth > videoWidth * elementHeight;
-
- // Convert to coordinates relative to the video by scaling.
- // (This matches the scaling used by 'object-fit: cover'.)
- //
- // This scaling is needed to translate from the in-browser x/y to the
- // on-device x/y.
- // - When the device screen has not been resized, this is simple: scale
- // the coordinates based on the ratio between the input video size and
- // the in-browser size.
- // - When the device screen has been resized, this scaling is still needed
- // even though the in-browser size and device size are identical. This
- // is due to the way WindowManager handles a resized screen, resized via
- // `adb shell wm size`:
- // - The ABS_X and ABS_Y max values of the screen retain their
- // original values equal to the value set when launching the device
- // (which equals the video size here).
- // - The sent ABS_X and ABS_Y values need to be scaled based on the
- // ratio between the max size (video size) and in-browser size.
- const scaling = scaleWidth ? videoWidth / elementWidth : videoHeight / elementHeight;
-
- var xArr = [];
- var yArr = [];
- var idArr = [];
- var slotArr = [];
-
- if (eventType == "mouse" || eventType == "point") {
- xArr.push(e.offsetX);
- yArr.push(e.offsetY);
-
- let thisId = -1;
- if (eventType == "point") {
- thisId = e.pointerId;
- }
-
- slotArr.push(0);
- idArr.push(thisId);
- } else if (eventType == "touch") {
- // touchstart: list of touch points that became active
- // touchmove: list of touch points that changed
- // touchend: list of touch points that were removed
- let changes = e.changedTouches;
- let rect = e.target.getBoundingClientRect();
- for (var i=0; i < changes.length; i++) {
- xArr.push(changes[i].pageX - rect.left);
- yArr.push(changes[i].pageY - rect.top);
- if (touchIdSlotMap.has(changes[i].identifier)) {
- let slot = touchIdSlotMap.get(changes[i].identifier);
-
- slotArr.push(slot);
- if (e.type == 'touchstart') {
- // error
- console.error('touchstart when already have slot');
- return;
- } else if (e.type == 'touchmove') {
- idArr.push(changes[i].identifier);
- } else if (e.type == 'touchend') {
- touchSlots[slot] = false;
- touchIdSlotMap.delete(changes[i].identifier);
- idArr.push(-1);
- }
- } else {
- if (e.type == 'touchstart') {
- let slot = -1;
- for (var j=0; j < touchSlots.length; j++) {
- if (!touchSlots[j]) {
- slot = j;
- break;
- }
- }
- if (slot == -1) {
- slot = touchSlots.length;
- touchSlots.push(true);
- }
- slotArr.push(slot);
- touchSlots[slot] = true;
- touchIdSlotMap.set(changes[i].identifier, slot);
- idArr.push(changes[i].identifier);
- } else if (e.type == 'touchmove') {
- // error
- console.error('touchmove when no slot');
- return;
- } else if (e.type == 'touchend') {
- // error
- console.error('touchend when no slot');
- return;
- }
- }
- }
- }
-
- for (var i=0; i < xArr.length; i++) {
- xArr[i] = xArr[i] * scaling;
- yArr[i] = yArr[i] * scaling;
-
- // Substract the offset produced by the difference in aspect ratio, if any.
- if (scaleWidth) {
- // Width was scaled, leaving excess content height, so subtract from y.
- yArr[i] -= (elementHeight * scaling - videoHeight) / 2;
- } else {
- // Height was scaled, leaving excess content width, so subtract from x.
- xArr[i] -= (elementWidth * scaling - videoWidth) / 2;
- }
-
- xArr[i] = Math.trunc(xArr[i]);
- yArr[i] = Math.trunc(yArr[i]);
- }
-
- // NOTE: Rotation is handled automatically because the CSS rotation through
- // transforms also rotates the coordinates of events on the object.
-
- const display_label = deviceDisplay.id;
-
- deviceConnection.sendMultiTouch(
- {idArr, xArr, yArr, down, slotArr, display_label});
- }
-
- function onKeyEvent(e) {
- e.preventDefault();
- console.assert(deviceConnection, 'Can\'t send key event without device');
- deviceConnection.sendKeyEvent(e.code, e.type);
- }
-}
-
-/******************************************************************************/
-
-function ConnectDeviceCb(dev_id) {
- console.log('Connect: ' + dev_id);
- // Hide the device selection screen
- document.getElementById('device-selector').style.display = 'none';
- // Show the device control screen
- document.getElementById('device-connection').style.visibility = 'visible';
- ConnectToDevice(dev_id);
-}
-
-function ShowNewDeviceList(device_ids) {
- let ul = document.getElementById('device-list');
- ul.innerHTML = "";
- let count = 1;
- let device_to_button_map = {};
- for (const dev_id of device_ids) {
- const button_id = 'connect_' + count++;
- ul.innerHTML += ('<li class="device_entry" title="Connect to ' + dev_id
- + '">' + dev_id + '<button id="' + button_id
- + '" >Connect</button></li>');
- device_to_button_map[dev_id] = button_id;
- }
-
- for (const [dev_id, button_id] of Object.entries(device_to_button_map)) {
- document.getElementById(button_id).addEventListener(
- 'click', evt => ConnectDeviceCb(dev_id));
- }
-}
-
-function UpdateDeviceList() {
- let url = ((location.protocol == 'http:') ? 'ws:' : 'wss:') + location.host +
- '/list_devices';
- let ws = new WebSocket(url);
- ws.onopen = () => {
- ws.send("give me those device ids");
- };
- ws.onmessage = msg => {
- let device_ids = JSON.parse(msg.data);
- ShowNewDeviceList(device_ids);
- };
-}
-
-// Get any devices that are already connected
-UpdateDeviceList();
-// Update the list at the user's request
-document.getElementById('refresh-list')
- .addEventListener('click', evt => UpdateDeviceList());
diff --git a/host/frontend/webrtc_operator/assets/js/cf_webrtc.js b/host/frontend/webrtc_operator/assets/js/cf_webrtc.js
deleted file mode 100644
index c83ee38..0000000
--- a/host/frontend/webrtc_operator/assets/js/cf_webrtc.js
+++ /dev/null
@@ -1,534 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-function createDataChannel(pc, label, onMessage) {
- console.log('creating data channel: ' + label);
- let dataChannel = pc.createDataChannel(label);
- // Return an object with a send function like that of the dataChannel, but
- // that only actually sends over the data channel once it has connected.
- return {
- channelPromise: new Promise((resolve, reject) => {
- dataChannel.onopen = (event) => {
- resolve(dataChannel);
- };
- dataChannel.onclose = () => {
- console.log(
- 'Data channel=' + label + ' state=' + dataChannel.readyState);
- };
- dataChannel.onmessage = onMessage ? onMessage : (msg) => {
- console.log('Data channel=' + label + ' data="' + msg.data + '"');
- };
- dataChannel.onerror = err => {
- reject(err);
- };
- }),
- send: function(msg) {
- this.channelPromise = this.channelPromise.then(channel => {
- channel.send(msg);
- return channel;
- })
- },
- };
-}
-
-function awaitDataChannel(pc, label, onMessage) {
- console.log('expecting data channel: ' + label);
- // Return an object with a send function like that of the dataChannel, but
- // that only actually sends over the data channel once it has connected.
- return {
- channelPromise: new Promise((resolve, reject) => {
- let prev_ondatachannel = pc.ondatachannel;
- pc.ondatachannel = ev => {
- let dataChannel = ev.channel;
- if (dataChannel.label == label) {
- dataChannel.onopen = (event) => {
- resolve(dataChannel);
- };
- dataChannel.onclose = () => {
- console.log(
- 'Data channel=' + label + ' state=' + dataChannel.readyState);
- };
- dataChannel.onmessage = onMessage ? onMessage : (msg) => {
- console.log('Data channel=' + label + ' data="' + msg.data + '"');
- };
- dataChannel.onerror = err => {
- reject(err);
- };
- } else if (prev_ondatachannel) {
- prev_ondatachannel(ev);
- }
- };
- }),
- send: function(msg) {
- this.channelPromise = this.channelPromise.then(channel => {
- channel.send(msg);
- return channel;
- })
- },
- };
-}
-
-class DeviceConnection {
- constructor(pc, control, media_stream) {
- this._pc = pc;
- this._control = control;
- this._media_stream = media_stream;
- // Disable the microphone by default
- this.useMic(false);
- this.useVideo(false);
- this._cameraDataChannel = pc.createDataChannel('camera-data-channel');
- this._cameraDataChannel.binaryType = 'arraybuffer'
- this._cameraInputQueue = new Array();
- var self = this;
- this._cameraDataChannel.onbufferedamountlow = () => {
- if (self._cameraInputQueue.length > 0) {
- self.sendCameraData(self._cameraInputQueue.shift());
- }
- }
- this._inputChannel = createDataChannel(pc, 'input-channel');
- this._adbChannel = createDataChannel(pc, 'adb-channel', (msg) => {
- if (this._onAdbMessage) {
- this._onAdbMessage(msg.data);
- } else {
- console.error('Received unexpected ADB message');
- }
- });
- this._controlChannel = awaitDataChannel(pc, 'device-control', (msg) => {
- if (this._onControlMessage) {
- this._onControlMessage(msg);
- } else {
- console.error('Received unexpected Control message');
- }
- });
- this._bluetoothChannel = createDataChannel(pc, 'bluetooth-channel', (msg) => {
- if (this._onBluetoothMessage) {
- this._onBluetoothMessage(msg.data);
- } else {
- console.error('Received unexpected Bluetooth message');
- }
- });
- this.sendCameraResolution();
- this._streams = {};
- this._streamPromiseResolvers = {};
-
- pc.addEventListener('track', e => {
- console.log('Got remote stream: ', e);
- for (const stream of e.streams) {
- this._streams[stream.id] = stream;
- if (this._streamPromiseResolvers[stream.id]) {
- for (let resolver of this._streamPromiseResolvers[stream.id]) {
- resolver();
- }
- delete this._streamPromiseResolvers[stream.id];
- }
- }
- });
- }
-
- set description(desc) {
- this._description = desc;
- }
-
- get description() {
- return this._description;
- }
-
- get imageCapture() {
- if (this._media_stream) {
- const track = this._media_stream.getVideoTracks()[0]
- return new ImageCapture(track);
- }
- return undefined;
- }
-
- get cameraWidth() {
- return this._x_res;
- }
-
- get cameraHeight() {
- return this._y_res;
- }
-
- get cameraEnabled() {
- if (this._media_stream) {
- return this._media_stream.getVideoTracks().some(track => track.enabled);
- }
- }
-
- getStream(stream_id) {
- return new Promise((resolve, reject) => {
- if (this._streams[stream_id]) {
- resolve(this._streams[stream_id]);
- } else {
- if (!this._streamPromiseResolvers[stream_id]) {
- this._streamPromiseResolvers[stream_id] = [];
- }
- this._streamPromiseResolvers[stream_id].push(resolve);
- }
- });
- }
-
- _sendJsonInput(evt) {
- this._inputChannel.send(JSON.stringify(evt));
- }
-
- sendMousePosition({x, y, down, display_label}) {
- this._sendJsonInput({
- type: 'mouse',
- down: down ? 1 : 0,
- x,
- y,
- display_label,
- });
- }
-
- // TODO (b/124121375): This should probably be an array of pointer events and
- // have different properties.
- sendMultiTouch({idArr, xArr, yArr, down, slotArr, display_label}) {
- this._sendJsonInput({
- type: 'multi-touch',
- id: idArr,
- x: xArr,
- y: yArr,
- down: down ? 1 : 0,
- slot: slotArr,
- display_label: display_label,
- });
- }
-
- sendKeyEvent(code, type) {
- this._sendJsonInput({type: 'keyboard', keycode: code, event_type: type});
- }
-
- disconnect() {
- this._pc.close();
- }
-
- // Sends binary data directly to the in-device adb daemon (skipping the host)
- sendAdbMessage(msg) {
- this._adbChannel.send(msg);
- }
-
- // Provide a callback to receive data from the in-device adb daemon
- onAdbMessage(cb) {
- this._onAdbMessage = cb;
- }
-
- // Send control commands to the device
- sendControlMessage(msg) {
- this._controlChannel.send(msg);
- }
-
- useMic(in_use) {
- if (this._media_stream) {
- this._media_stream.getAudioTracks().forEach(track => track.enabled = in_use);
- }
- }
-
- useVideo(in_use) {
- if (this._media_stream) {
- this._media_stream.getVideoTracks().forEach(track => track.enabled = in_use);
- }
- }
-
- sendCameraResolution() {
- if (this._media_stream) {
- const cameraTracks = this._media_stream.getVideoTracks();
- if (cameraTracks.length > 0) {
- const settings = cameraTracks[0].getSettings();
- this._x_res = settings.width;
- this._y_res = settings.height;
- this.sendControlMessage(JSON.stringify({
- command: 'camera_settings',
- width: settings.width,
- height: settings.height,
- frame_rate: settings.frameRate,
- facing: settings.facingMode
- }));
- }
- }
- }
-
- sendOrQueueCameraData(data) {
- if (this._cameraDataChannel.bufferedAmount > 0 || this._cameraInputQueue.length > 0) {
- this._cameraInputQueue.push(data);
- } else {
- this.sendCameraData(data);
- }
- }
-
- sendCameraData(data) {
- const MAX_SIZE = 65535;
- const END_MARKER = 'EOF';
- for (let i = 0; i < data.byteLength; i += MAX_SIZE) {
- // range is clamped to the valid index range
- this._cameraDataChannel.send(data.slice(i, i + MAX_SIZE));
- }
- this._cameraDataChannel.send(END_MARKER);
- }
-
- // Provide a callback to receive control-related comms from the device
- onControlMessage(cb) {
- this._onControlMessage = cb;
- }
-
- sendBluetoothMessage(msg) {
- this._bluetoothChannel.send(msg);
- }
-
- onBluetoothMessage(cb) {
- this._onBluetoothMessage = cb;
- }
-
- // Provide a callback to receive connectionstatechange states.
- onConnectionStateChange(cb) {
- this._pc.addEventListener(
- 'connectionstatechange',
- evt => cb(this._pc.connectionState));
- }
-}
-
-
-class WebRTCControl {
- constructor({
- wsUrl = '',
- }) {
- /*
- * Private attributes:
- *
- * _wsPromise: promises the underlying websocket, should resolve when the
- * socket passes to OPEN state, will be rejecte/replaced by a
- * rejected promise if an error is detected on the socket.
- *
- * _onOffer
- * _onIceCandidate
- */
-
- this._promiseResolvers = {};
-
- this._wsPromise = new Promise((resolve, reject) => {
- let ws = new WebSocket(wsUrl);
- ws.onopen = () => {
- console.info(`Connected to ${wsUrl}`);
- resolve(ws);
- };
- ws.onerror = evt => {
- console.error('WebSocket error:', evt);
- reject(evt);
- // If the promise was already resolved the previous line has no effect
- this._wsPromise = Promise.reject(new Error(evt));
- };
- ws.onmessage = e => {
- let data = JSON.parse(e.data);
- this._onWebsocketMessage(data);
- };
- });
- }
-
- _onWebsocketMessage(message) {
- const type = message.message_type;
- if (message.error) {
- console.error(message.error);
- this._on_connection_failed(message.error);
- return;
- }
- switch (type) {
- case 'config':
- this._infra_config = message;
- break;
- case 'device_info':
- if (this._on_device_available) {
- this._on_device_available(message.device_info);
- delete this._on_device_available;
- } else {
- console.error('Received unsolicited device info');
- }
- break;
- case 'device_msg':
- this._onDeviceMessage(message.payload);
- break;
- default:
- console.error('Unrecognized message type from server: ', type);
- this._on_connection_failed('Unrecognized message type from server: ' + type);
- console.error(message);
- }
- }
-
- _onDeviceMessage(message) {
- let type = message.type;
- switch (type) {
- case 'offer':
- if (this._onOffer) {
- this._onOffer({type: 'offer', sdp: message.sdp});
- } else {
- console.error('Receive offer, but nothing is wating for it');
- }
- break;
- case 'ice-candidate':
- if (this._onIceCandidate) {
- this._onIceCandidate(new RTCIceCandidate({
- sdpMid: message.mid,
- sdpMLineIndex: message.mLineIndex,
- candidate: message.candidate
- }));
- } else {
- console.error('Received ice candidate but nothing is waiting for it');
- }
- break;
- default:
- console.error('Unrecognized message type from device: ', type);
- }
- }
-
- async _wsSendJson(obj) {
- let ws = await this._wsPromise;
- return ws.send(JSON.stringify(obj));
- }
- async _sendToDevice(payload) {
- this._wsSendJson({message_type: 'forward', payload});
- }
-
- onOffer(cb) {
- this._onOffer = cb;
- }
-
- onIceCandidate(cb) {
- this._onIceCandidate = cb;
- }
-
- async requestDevice(device_id) {
- return new Promise((resolve, reject) => {
- this._on_device_available = (deviceInfo) => resolve({
- deviceInfo,
- infraConfig: this._infra_config,
- });
- this._on_connection_failed = (error) => reject(error);
- this._wsSendJson({
- message_type: 'connect',
- device_id,
- });
- });
- }
-
- ConnectDevice() {
- console.log('ConnectDevice');
- this._sendToDevice({type: 'request-offer'});
- }
-
- /**
- * Sends a remote description to the device.
- */
- async sendClientDescription(desc) {
- console.log('sendClientDescription');
- this._sendToDevice({type: 'answer', sdp: desc.sdp});
- }
-
- /**
- * Sends an ICE candidate to the device
- */
- async sendIceCandidate(candidate) {
- this._sendToDevice({type: 'ice-candidate', candidate});
- }
-}
-
-function createPeerConnection(infra_config) {
- let pc_config = {iceServers: []};
- for (const stun of infra_config.ice_servers) {
- pc_config.iceServers.push({urls: 'stun:' + stun});
- }
- let pc = new RTCPeerConnection(pc_config);
-
- pc.addEventListener('icecandidate', evt => {
- console.log('Local ICE Candidate: ', evt.candidate);
- });
- pc.addEventListener('iceconnectionstatechange', evt => {
- console.log(`ICE State Change: ${pc.iceConnectionState}`);
- });
- pc.addEventListener(
- 'connectionstatechange',
- evt =>
- console.log(`WebRTC Connection State Change: ${pc.connectionState}`));
- return pc;
-}
-
-export async function Connect(deviceId, options) {
- let control = new WebRTCControl(options);
- let requestRet = await control.requestDevice(deviceId);
- let deviceInfo = requestRet.deviceInfo;
- let infraConfig = requestRet.infraConfig;
- console.log('Device available:');
- console.log(deviceInfo);
- let pc_config = {iceServers: []};
- if (infraConfig.ice_servers && infraConfig.ice_servers.length > 0) {
- for (const server of infraConfig.ice_servers) {
- pc_config.iceServers.push(server);
- }
- }
- let pc = createPeerConnection(infraConfig, control);
-
- let mediaStream;
- try {
- mediaStream =
- await navigator.mediaDevices.getUserMedia({video: true, audio: true});
- const tracks = mediaStream.getTracks();
- tracks.forEach(track => {
- console.log(`Using ${track.kind} device: ${track.label}`);
- pc.addTrack(track, mediaStream);
- });
- } catch (e) {
- console.error("Failed to open audio device: ", e);
- }
-
- let deviceConnection = new DeviceConnection(pc, control, mediaStream);
- deviceConnection.description = deviceInfo;
- async function acceptOfferAndReplyAnswer(offer) {
- try {
- await pc.setRemoteDescription(offer);
- let answer = await pc.createAnswer();
- console.log('Answer: ', answer);
- await pc.setLocalDescription(answer);
- await control.sendClientDescription(answer);
- } catch (e) {
- console.error('Error establishing WebRTC connection: ', e)
- throw e;
- }
- }
- control.onOffer(desc => {
- console.log('Offer: ', desc);
- acceptOfferAndReplyAnswer(desc);
- });
- control.onIceCandidate(iceCandidate => {
- console.log(`Remote ICE Candidate: `, iceCandidate);
- pc.addIceCandidate(iceCandidate);
- });
-
- pc.addEventListener('icecandidate', evt => {
- if (evt.candidate) control.sendIceCandidate(evt.candidate);
- });
- let connected_promise = new Promise((resolve, reject) => {
- pc.addEventListener('connectionstatechange', evt => {
- let state = pc.connectionState;
- if (state == 'connected') {
- resolve(deviceConnection);
- } else if (state == 'failed') {
- reject(evt);
- }
- });
- });
- control.ConnectDevice();
-
- return connected_promise;
-}
diff --git a/host/frontend/webrtc_operator/assets/js/controls.js b/host/frontend/webrtc_operator/assets/js/controls.js
deleted file mode 100644
index 31db046..0000000
--- a/host/frontend/webrtc_operator/assets/js/controls.js
+++ /dev/null
@@ -1,38 +0,0 @@
-/*
- * Copyright (C) 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-function createToggleControl(elm, iconName, onChangeCb) {
- let icon = document.createElement("span");
- icon.classList.add("toggle-control-icon");
- icon.classList.add("material-icons-outlined");
- if (iconName) {
- icon.appendChild(document.createTextNode(iconName));
- }
- elm.appendChild(icon);
- let toggle = document.createElement("label");
- toggle.classList.add("toggle-control-switch");
- let input = document.createElement("input");
- input.type = "checkbox";
- toggle.appendChild(input);
- let slider = document.createElement("span");
- slider.classList.add("toggle-control-slider");
- toggle.appendChild(slider);
- elm.classList.add("toggle-control");
- elm.appendChild(toggle);
- if (onChangeCb) {
- input.onchange = e => onChangeCb(e.target.checked);
- }
-}
diff --git a/host/frontend/webrtc_operator/assets/js/index.js b/host/frontend/webrtc_operator/assets/js/index.js
new file mode 100644
index 0000000..c04f308
--- /dev/null
+++ b/host/frontend/webrtc_operator/assets/js/index.js
@@ -0,0 +1,100 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+'use strict';
+
+class DeviceListApp {
+ #url;
+ #selectDeviceCb;
+
+ constructor({url, selectDeviceCb}) {
+ this.#url = url;
+ this.#selectDeviceCb = selectDeviceCb;
+ }
+
+ start() {
+ // Get any devices that are already connected
+ this.#UpdateDeviceList();
+
+ // Update the list at the user's request
+ document.getElementById('refresh-list')
+ .addEventListener('click', evt => this.#UpdateDeviceList());
+ }
+
+ async #UpdateDeviceList() {
+ try {
+ const device_ids = await fetch(this.#url, {
+ method: 'GET',
+ cache: 'no-cache',
+ redirect: 'follow',
+ });
+ this.#ShowNewDeviceList(await device_ids.json());
+ } catch (e) {
+ console.error('Error getting list of device ids: ', e);
+ }
+ }
+
+ #ShowNewDeviceList(device_ids) {
+ let ul = document.getElementById('device-list');
+ ul.innerHTML = '';
+ let count = 1;
+ let device_to_button_map = {};
+ for (const devId of device_ids) {
+ const buttonId = 'connect_' + count++;
+ let entry = this.#createDeviceEntry(devId, buttonId);
+ ul.appendChild(entry);
+ device_to_button_map[devId] = buttonId;
+ }
+
+ for (const [devId, buttonId] of Object.entries(device_to_button_map)) {
+ let button = document.getElementById(buttonId);
+ button.addEventListener('click', evt => {
+ this.#selectDeviceCb(devId);
+ });
+ }
+ }
+
+ #createDeviceEntry(devId, buttonId) {
+ let li = document.createElement('li');
+ li.className = 'device_entry';
+ li.title = 'Connect to ' + devId;
+ let div = document.createElement('div');
+ let span = document.createElement('span');
+ span.appendChild(document.createTextNode(devId));
+ let button = document.createElement('button');
+ button.id = buttonId;
+ button.appendChild(document.createTextNode('Connect'));
+ div.appendChild(span);
+ div.appendChild(button);
+ li.appendChild(div);
+ return li;
+ }
+} // DeviceListApp
+
+window.addEventListener('load', e => {
+ let listDevicesUrl = '/devices';
+ let selectDeviceCb = deviceId => {
+ return new Promise((resolve, reject) => {
+ let client = window.open(`client.html?deviceId=${deviceId}`, deviceId);
+ client.addEventListener('load', evt => {
+ console.log('loaded');
+ resolve();
+ });
+ });
+ };
+ let deviceListApp = new DeviceListApp({url: listDevicesUrl, selectDeviceCb});
+ deviceListApp.start();
+});
diff --git a/host/frontend/webrtc_operator/assets/js/server_connector.js b/host/frontend/webrtc_operator/assets/js/server_connector.js
new file mode 100644
index 0000000..ff19a0a
--- /dev/null
+++ b/host/frontend/webrtc_operator/assets/js/server_connector.js
@@ -0,0 +1,285 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+'use strict';
+
+// The public elements in this file implement the Server Connector Interface,
+// part of the contract between the signaling server and the webrtc client.
+// No changes that break backward compatibility are allowed here. Any new
+// features must be added as a new function/class in the interface. Any
+// additions to the interface must be checked for existence by the client before
+// using it.
+
+// The id of the device the client is supposed to connect to.
+// The List Devices page in the signaling server may choose any way to pass the
+// device id to the client page, this function retrieves that information once
+// the client loaded.
+// In this case the device id is passed as a parameter in the url.
+export function deviceId() {
+ const urlParams = new URLSearchParams(window.location.search);
+ return urlParams.get('deviceId');
+}
+
+// Creates a connector capable of communicating with the signaling server.
+export async function createConnector() {
+ try {
+ let ws = await connectWs();
+ console.debug(`Connected to ${ws.url}`);
+ return new WebsocketConnector(ws);
+ } catch (e) {
+ console.error('WebSocket error:', e);
+ }
+ console.warn('Failed to connect websocket, trying polling instead');
+
+ return new PollingConnector();
+}
+
+// A connector object provides high level functions for communicating with the
+// signaling server, while hiding away implementation details.
+// This class is an interface and shouldn't be instantiated direclty.
+// Only the public methods present in this class form part of the Server
+// Connector Interface, any implementations of the interface are considered
+// internal and not accessible to client code.
+class Connector {
+ constructor() {
+ if (this.constructor == Connector) {
+ throw new Error('Connector is an abstract class');
+ }
+ }
+
+ // Selects a particular device in the signaling server and opens the signaling
+ // channel with it (but doesn't send any message to the device). Returns a
+ // promise to an object with the following properties:
+ // - deviceInfo: The info object provided by the device when it registered
+ // with the server.
+ // - infraConfig: The server's infrastructure configuration (mainly STUN and
+ // TURN servers)
+ // The promise may take a long time to resolve if, for example, the server
+ // decides to wait for a device with the provided id to register with it. The
+ // promise may be rejected if there are connectivity issues, a device with
+ // that id doesn't exist or this client doesn't have rights to access that
+ // device.
+ async requestDevice(deviceId) {
+ throw 'Not implemented!';
+ }
+
+ // Sends a message to the device selected with requestDevice. It's an error to
+ // call this function before the promise from requestDevice() has resolved.
+ // Returns an empty promise that is rejected when the message can not be
+ // delivered, either because the device has not been requested yet or because
+ // of connectivity issues.
+ async sendToDevice(msg) {
+ throw 'Not implemented!';
+ }
+}
+
+// End of Server Connector Interface.
+
+// The following code is internal and shouldn't be accessed outside this file.
+
+function httpUrl(path) {
+ return location.protocol + '//' + location.host + '/' + path;
+}
+
+function websocketUrl(path) {
+ return ((location.protocol == 'http:') ? 'ws://' : 'wss://') + location.host +
+ '/' + path;
+}
+
+const kPollConfigUrl = httpUrl('infra_config');
+const kPollConnectUrl = httpUrl('connect');
+const kPollForwardUrl = httpUrl('forward');
+const kPollMessagesUrl = httpUrl('poll_messages');
+
+async function connectWs() {
+ return new Promise((resolve, reject) => {
+ let url = websocketUrl('connect_client');
+ let ws = new WebSocket(url);
+ ws.onopen = () => {
+ resolve(ws);
+ };
+ ws.onerror = evt => {
+ reject(evt);
+ };
+ });
+}
+
+async function ajaxPostJson(url, data) {
+ const response = await fetch(url, {
+ method: 'POST',
+ cache: 'no-cache',
+ headers: {'Content-Type': 'application/json'},
+ redirect: 'follow',
+ body: JSON.stringify(data),
+ });
+ return response.json();
+}
+
+// Implementation of the connector interface using websockets
+class WebsocketConnector extends Connector {
+ #websocket;
+ #futures = {};
+ #onDeviceMsgCb = msg =>
+ console.error('Received device message without registered listener');
+
+ onDeviceMsg(cb) {
+ this.#onDeviceMsgCb = cb;
+ }
+
+ constructor(ws) {
+ super();
+ ws.onmessage = e => {
+ let data = JSON.parse(e.data);
+ this.#onWebsocketMessage(data);
+ };
+ this.#websocket = ws;
+ }
+
+ async requestDevice(deviceId) {
+ return new Promise((resolve, reject) => {
+ this.#futures.onDeviceAvailable = (device) => resolve(device);
+ this.#futures.onConnectionFailed = (error) => reject(error);
+ this.#wsSendJson({
+ message_type: 'connect',
+ device_id: deviceId,
+ });
+ });
+ }
+
+ async sendToDevice(msg) {
+ return this.#wsSendJson({message_type: 'forward', payload: msg});
+ }
+
+ #onWebsocketMessage(message) {
+ const type = message.message_type;
+ if (message.error) {
+ console.error(message.error);
+ this.#futures.onConnectionFailed(message.error);
+ return;
+ }
+ switch (type) {
+ case 'config':
+ this.#futures.infraConfig = message;
+ break;
+ case 'device_info':
+ if (this.#futures.onDeviceAvailable) {
+ this.#futures.onDeviceAvailable({
+ deviceInfo: message.device_info,
+ infraConfig: this.#futures.infraConfig,
+ });
+ delete this.#futures.onDeviceAvailable;
+ } else {
+ console.error('Received unsolicited device info');
+ }
+ break;
+ case 'device_msg':
+ this.#onDeviceMsgCb(message.payload);
+ break;
+ default:
+ console.error('Unrecognized message type from server: ', type);
+ this.#futures.onConnectionFailed(
+ 'Unrecognized message type from server: ' + type);
+ console.error(message);
+ }
+ }
+
+ async #wsSendJson(obj) {
+ return this.#websocket.send(JSON.stringify(obj));
+ }
+}
+
+// Implementation of the Connector interface using HTTP long polling
+class PollingConnector extends Connector {
+ #connId = undefined;
+ #config = undefined;
+ #pollerSchedule;
+ #onDeviceMsgCb = msg =>
+ console.error('Received device message without registered listener');
+
+ onDeviceMsg(cb) {
+ this.#onDeviceMsgCb = cb;
+ }
+
+ constructor() {
+ super();
+ }
+
+ async requestDevice(deviceId) {
+ let config = await this.#getConfig();
+ let response = await ajaxPostJson(kPollConnectUrl, {device_id: deviceId});
+ this.#connId = response.connection_id;
+
+ this.#startPolling();
+
+ return {
+ deviceInfo: response.device_info,
+ infraConfig: config,
+ };
+ }
+
+ async sendToDevice(msg) {
+ // Forward messages act like polling messages as well
+ let device_messages = await this.#forward(msg);
+ for (const message of device_messages) {
+ this.#onDeviceMsgCb(message);
+ }
+ }
+
+ async #getConfig() {
+ if (this.#config === undefined) {
+ this.#config = await (await fetch(kPollConfigUrl, {
+ method: 'GET',
+ redirect: 'follow',
+ })).json();
+ }
+ return this.#config;
+ }
+
+ async #forward(msg) {
+ return await ajaxPostJson(kPollForwardUrl, {
+ connection_id: this.#connId,
+ payload: msg,
+ });
+ }
+
+ async #pollMessages() {
+ return await ajaxPostJson(kPollMessagesUrl, {
+ connection_id: this.#connId,
+ });
+ }
+
+ #startPolling() {
+ if (this.#pollerSchedule !== undefined) {
+ return;
+ }
+
+ let currentPollDelay = 1000;
+ let pollerRoutine = async () => {
+ let messages = await this.#pollMessages();
+
+ // Do exponential backoff on the polling up to 60 seconds
+ currentPollDelay = Math.min(60000, 2 * currentPollDelay);
+ for (const message of messages) {
+ this.#onDeviceMsgCb(message);
+ // There is at least one message, poll sooner
+ currentPollDelay = 1000;
+ }
+ this.#pollerSchedule = setTimeout(pollerRoutine, currentPollDelay);
+ };
+
+ this.#pollerSchedule = setTimeout(pollerRoutine, currentPollDelay);
+ }
+}
diff --git a/host/frontend/webrtc_operator/client_handler.cpp b/host/frontend/webrtc_operator/client_handler.cpp
index 7b631a6..a69241c 100644
--- a/host/frontend/webrtc_operator/client_handler.cpp
+++ b/host/frontend/webrtc_operator/client_handler.cpp
@@ -15,6 +15,9 @@
#include "host/frontend/webrtc_operator/client_handler.h"
+#include <algorithm>
+#include <random>
+
#include <android-base/logging.h>
#include "host/frontend/webrtc_operator/constants/signaling_constants.h"
@@ -22,27 +25,42 @@
namespace cuttlefish {
-ClientHandler::ClientHandler(struct lws* wsi, DeviceRegistry* registry,
+namespace {
+std::string RandomClientSecret(size_t len) {
+ static constexpr auto chars =
+ "0123456789"
+ "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
+ "abcdefghijklmnopqrstuvwxyz";
+ std::string ret(len, '\0');
+ std::default_random_engine e{std::random_device{}()};
+ std::uniform_int_distribution<int> random{
+ 0, static_cast<int>(std::strlen(chars)) - 1};
+ std::generate_n(ret.begin(), len, [&]() { return chars[random(e)]; });
+ return ret;
+}
+}
+
+ClientWSHandler::ClientWSHandler(struct lws* wsi, DeviceRegistry* registry,
const ServerConfig& server_config)
: SignalHandler(wsi, registry, server_config),
device_handler_(),
client_id_(0) {}
-void ClientHandler::OnClosed() {
+void ClientWSHandler::OnClosed() {
auto device_handler = device_handler_.lock();
if (device_handler) {
device_handler->SendClientDisconnectMessage(client_id_);
}
}
-void ClientHandler::SendDeviceMessage(const Json::Value& device_message) {
+void ClientWSHandler::SendDeviceMessage(const Json::Value& device_message) {
Json::Value message;
message[webrtc_signaling::kTypeField] = webrtc_signaling::kDeviceMessageType;
message[webrtc_signaling::kPayloadField] = device_message;
Reply(message);
}
-void ClientHandler::handleMessage(const std::string& type,
+void ClientWSHandler::handleMessage(const std::string& type,
const Json::Value& message) {
if (type == webrtc_signaling::kConnectType) {
handleConnectionRequest(message);
@@ -53,7 +71,7 @@
}
}
-void ClientHandler::handleConnectionRequest(const Json::Value& message) {
+void ClientWSHandler::handleConnectionRequest(const Json::Value& message) {
if (client_id_ > 0) {
LogAndReplyError(
"Attempt to connect to multiple devices over same websocket");
@@ -90,7 +108,7 @@
Reply(device_info_reply);
}
-void ClientHandler::handleForward(const Json::Value& message) {
+void ClientWSHandler::handleForward(const Json::Value& message) {
if (client_id_ == 0) {
LogAndReplyError("Forward failed: No device asociated to client");
Close();
@@ -112,14 +130,232 @@
message[webrtc_signaling::kPayloadField]);
}
-ClientHandlerFactory::ClientHandlerFactory(DeviceRegistry* registry,
+ClientWSHandlerFactory::ClientWSHandlerFactory(DeviceRegistry* registry,
const ServerConfig& server_config)
: registry_(registry),
server_config_(server_config) {}
-std::shared_ptr<WebSocketHandler> ClientHandlerFactory::Build(struct lws* wsi) {
+std::shared_ptr<WebSocketHandler> ClientWSHandlerFactory::Build(struct lws* wsi) {
return std::shared_ptr<WebSocketHandler>(
- new ClientHandler(wsi, registry_, server_config_));
+ new ClientWSHandler(wsi, registry_, server_config_));
+}
+
+/******************************************************************************/
+
+class PollConnectionHandler : public ClientHandler {
+ public:
+ PollConnectionHandler() = default;
+
+ void SendDeviceMessage(const Json::Value& message) override {
+ constexpr size_t kMaxMessagesInQueue = 1000;
+ if (messages_.size() > kMaxMessagesInQueue) {
+ LOG(ERROR) << "Polling client " << client_id_ << " reached "
+ << kMaxMessagesInQueue
+ << " messages queued. Started to drop messages.";
+ return;
+ }
+ messages_.push_back(message);
+ }
+
+ std::vector<Json::Value> PollMessages() {
+ std::vector<Json::Value> ret;
+ std::swap(ret, messages_);
+ return ret;
+ }
+
+ void SetDeviceHandler(std::weak_ptr<DeviceHandler> device_handler) {
+ device_handler_ = device_handler;
+ }
+
+ void SetClientId(size_t client_id) { client_id_ = client_id; }
+
+ size_t client_id() const { return client_id_; }
+ std::shared_ptr<DeviceHandler> device_handler() const {
+ return device_handler_.lock();
+ }
+
+ private:
+ size_t client_id_ = 0;
+ std::weak_ptr<DeviceHandler> device_handler_;
+ std::vector<Json::Value> messages_;
+};
+
+std::shared_ptr<PollConnectionHandler> PollConnectionStore::Get(
+ const std::string& conn_id) const {
+ if (!handlers_.count(conn_id)) {
+ return nullptr;
+ }
+ return handlers_.at(conn_id);
+}
+
+std::string PollConnectionStore::Add(std::shared_ptr<PollConnectionHandler> handler) {
+ std::string conn_id;
+ do {
+ conn_id = RandomClientSecret(64);
+ } while (handlers_.count(conn_id));
+ handlers_[conn_id] = handler;
+ return conn_id;
+}
+
+ClientDynHandler::ClientDynHandler(struct lws* wsi,
+ PollConnectionStore* poll_store)
+ : DynHandler(wsi), poll_store_(poll_store) {}
+
+HttpStatusCode ClientDynHandler::DoGet() {
+ // No message from the client uses the GET method because all of them
+ // change the server state somehow
+ return HttpStatusCode::MethodNotAllowed;
+}
+
+void ClientDynHandler::Reply(const Json::Value& json) {
+ Json::StreamWriterBuilder factory;
+ auto replyAsString = Json::writeString(factory, json);
+ AppendDataOut(replyAsString);
+}
+
+void ClientDynHandler::ReplyError(const std::string& message) {
+ LOG(ERROR) << message;
+ Json::Value reply;
+ reply["type"] = "error";
+ reply["error"] = message;
+ Reply(reply);
+}
+
+HttpStatusCode ClientDynHandler::DoPost() {
+ auto& data = GetDataIn();
+ Json::Value json_message;
+ std::shared_ptr<PollConnectionHandler> poll_handler;
+ if (data.size() > 0) {
+ Json::CharReaderBuilder builder;
+ std::unique_ptr<Json::CharReader> json_reader(builder.newCharReader());
+ std::string error_message;
+ if (!json_reader->parse(data.c_str(), data.c_str() + data.size(), &json_message,
+ &error_message)) {
+ ReplyError("Error parsing JSON: " + error_message);
+ // Rate limiting would be a good idea here
+ return HttpStatusCode::BadRequest;
+ }
+
+ std::string conn_id;
+ if (json_message.isMember(webrtc_signaling::kClientSecretField)) {
+ conn_id =
+ json_message[webrtc_signaling::kClientSecretField].asString();
+ poll_handler = poll_store_->Get(conn_id);
+ if (!poll_handler) {
+ ReplyError("Error: Unknown connection id" + conn_id);
+ return HttpStatusCode::Unauthorized;
+ }
+ }
+ }
+ return DoPostInner(poll_handler, json_message);
+}
+
+HttpStatusCode ClientDynHandler::Poll(
+ std::shared_ptr<PollConnectionHandler> poll_handler) {
+ if (!poll_handler) {
+ ReplyError("Poll failed: No device associated to client");
+ return HttpStatusCode::Unauthorized;
+ }
+ auto messages = poll_handler->PollMessages();
+ Json::Value reply(Json::arrayValue);
+ for (auto& msg : messages) {
+ reply.append(msg);
+ }
+ Reply(reply);
+ return HttpStatusCode::Ok;
+}
+
+ConnectHandler::ConnectHandler(struct lws* wsi, DeviceRegistry* registry,
+ PollConnectionStore* poll_store)
+ : ClientDynHandler(wsi, poll_store), registry_(registry) {}
+
+HttpStatusCode ConnectHandler::DoPostInner(
+ std::shared_ptr<PollConnectionHandler> poll_handler,
+ const Json::Value& message) {
+ if (!message.isMember(webrtc_signaling::kDeviceIdField) ||
+ !message[webrtc_signaling::kDeviceIdField].isString()) {
+ ReplyError("Invalid connection request: Missing device id");
+ return HttpStatusCode::BadRequest;
+ }
+ auto device_id = message[webrtc_signaling::kDeviceIdField].asString();
+
+ auto device_handler = registry_->GetDevice(device_id);
+ if (!device_handler) {
+ ReplyError("Connection failed: Device not found: '" + device_id + "'");
+ return HttpStatusCode::NotFound;
+ }
+
+ poll_handler = std::make_shared<PollConnectionHandler>();
+ poll_handler->SetClientId(device_handler->RegisterClient(poll_handler));
+ poll_handler->SetDeviceHandler(device_handler);
+ auto conn_id = poll_store_->Add(poll_handler);
+
+ Json::Value device_info_reply;
+ device_info_reply[webrtc_signaling::kClientSecretField] = conn_id;
+ device_info_reply[webrtc_signaling::kTypeField] =
+ webrtc_signaling::kDeviceInfoType;
+ device_info_reply[webrtc_signaling::kDeviceInfoField] =
+ device_handler->device_info();
+ Reply(device_info_reply);
+
+ return HttpStatusCode::Ok;
+}
+
+ForwardHandler::ForwardHandler(struct lws* wsi,
+ PollConnectionStore* poll_store)
+ : ClientDynHandler(wsi, poll_store) {}
+
+HttpStatusCode ForwardHandler::DoPostInner(
+ std::shared_ptr<PollConnectionHandler> poll_handler,
+ const Json::Value& message) {
+ if (!poll_handler) {
+ ReplyError("Forward failed: No device associated to client");
+ return HttpStatusCode::Unauthorized;
+ }
+ auto client_id = poll_handler->client_id();
+ if (client_id == 0) {
+ ReplyError("Forward failed: No device associated to client");
+ return HttpStatusCode::Unauthorized;
+ }
+ if (!message.isMember(webrtc_signaling::kPayloadField)) {
+ ReplyError("Forward failed: No payload present in message");
+ return HttpStatusCode::BadRequest;
+ }
+ auto device_handler = poll_handler->device_handler();
+ if (!device_handler) {
+ ReplyError("Forward failed: Device disconnected");
+ return HttpStatusCode::NotFound;
+ }
+ device_handler->SendClientMessage(client_id,
+ message[webrtc_signaling::kPayloadField]);
+ // Don't waste an HTTP session returning nothing, send any pending device
+ // messages to the client instead.
+ return Poll(poll_handler);
+}
+
+PollHandler::PollHandler(struct lws* wsi, PollConnectionStore* poll_store)
+ : ClientDynHandler(wsi, poll_store) {}
+
+HttpStatusCode PollHandler::DoPostInner(
+ std::shared_ptr<PollConnectionHandler> poll_handler,
+ const Json::Value& /*message*/) {
+ return Poll(poll_handler);
+}
+
+ConfigHandler::ConfigHandler(struct lws* wsi, const ServerConfig& server_config)
+ : DynHandler(wsi), server_config_(server_config) {}
+
+HttpStatusCode ConfigHandler::DoGet() {
+ Json::Value reply = server_config_.ToJson();
+ reply[webrtc_signaling::kTypeField] = webrtc_signaling::kConfigType;
+ Json::StreamWriterBuilder factory;
+ auto replyAsString = Json::writeString(factory, reply);
+ AppendDataOut(replyAsString);
+ return HttpStatusCode::Ok;
+}
+
+HttpStatusCode ConfigHandler::DoPost() {
+ return HttpStatusCode::MethodNotAllowed;
}
} // namespace cuttlefish
diff --git a/host/frontend/webrtc_operator/client_handler.h b/host/frontend/webrtc_operator/client_handler.h
index b10f3e8..2cb6a0e 100644
--- a/host/frontend/webrtc_operator/client_handler.h
+++ b/host/frontend/webrtc_operator/client_handler.h
@@ -27,18 +27,27 @@
namespace cuttlefish {
class DeviceHandler;
-class ClientHandler : public SignalHandler,
- public std::enable_shared_from_this<ClientHandler> {
+
+class ClientHandler {
public:
- ClientHandler(struct lws* wsi, DeviceRegistry* registry,
- const ServerConfig& server_config);
- void SendDeviceMessage(const Json::Value& message);
+ virtual ~ClientHandler() = default;
+ virtual void SendDeviceMessage(const Json::Value& message) = 0;
+};
+
+class ClientWSHandler : public ClientHandler,
+ public SignalHandler,
+ public std::enable_shared_from_this<ClientHandler> {
+ public:
+ ClientWSHandler(struct lws* wsi, DeviceRegistry* registry,
+ const ServerConfig& server_config);
+
+ void SendDeviceMessage(const Json::Value& message) override;
void OnClosed() override;
protected:
void handleMessage(const std::string& type,
- const Json::Value& message) override;
+ const Json::Value& message) override;
private:
void handleConnectionRequest(const Json::Value& message);
@@ -50,14 +59,91 @@
size_t client_id_;
};
-class ClientHandlerFactory : public WebSocketHandlerFactory {
+class ClientWSHandlerFactory : public WebSocketHandlerFactory {
public:
- ClientHandlerFactory(DeviceRegistry* registry,
- const ServerConfig& server_config);
+ ClientWSHandlerFactory(DeviceRegistry* registry,
+ const ServerConfig& server_config);
std::shared_ptr<WebSocketHandler> Build(struct lws* wsi) override;
private:
DeviceRegistry* registry_;
const ServerConfig& server_config_;
};
+
+class PollConnectionHandler;
+
+class PollConnectionStore {
+ public:
+ PollConnectionStore() = default;
+
+ std::shared_ptr<PollConnectionHandler> Get(const std::string& conn_id) const;
+ std::string Add(std::shared_ptr<PollConnectionHandler> handler);
+ private:
+ std::map<std::string, std::shared_ptr<PollConnectionHandler>>
+ handlers_;
+};
+
+class ClientDynHandler : public DynHandler,
+ public std::enable_shared_from_this<ClientHandler> {
+ public:
+ ClientDynHandler(struct lws* wsi, PollConnectionStore* poll_store);
+
+ HttpStatusCode DoGet() override;
+ HttpStatusCode DoPost() override;
+
+ protected:
+ virtual HttpStatusCode DoPostInner(std::shared_ptr<PollConnectionHandler>,
+ const Json::Value&) = 0;
+ // In the base class because it's shared by some of the subclasses
+ HttpStatusCode Poll(std::shared_ptr<PollConnectionHandler>);
+
+ void Reply(const Json::Value& json);
+ void ReplyError(const std::string& message);
+ bool ParseInput();
+
+ PollConnectionStore* poll_store_;
+};
+
+class ConnectHandler : public ClientDynHandler {
+ public:
+ ConnectHandler(struct lws* wsi, DeviceRegistry* registry,
+ PollConnectionStore* poll_store);
+
+ protected:
+ HttpStatusCode DoPostInner(std::shared_ptr<PollConnectionHandler>,
+ const Json::Value&) override;
+ private:
+ DeviceRegistry* registry_;
+};
+
+class ForwardHandler : public ClientDynHandler {
+ public:
+ ForwardHandler(struct lws* wsi, PollConnectionStore* poll_store);
+
+ protected:
+ HttpStatusCode DoPostInner(std::shared_ptr<PollConnectionHandler>,
+ const Json::Value&) override;
+};
+
+class PollHandler : public ClientDynHandler {
+ public:
+ PollHandler(struct lws* wsi, PollConnectionStore* poll_store);
+
+ protected:
+ HttpStatusCode DoPostInner(std::shared_ptr<PollConnectionHandler>,
+ const Json::Value&) override;
+};
+
+class ConfigHandler : public DynHandler {
+ public:
+ ConfigHandler(struct lws* wsi, const ServerConfig& server_config);
+
+ HttpStatusCode DoGet() override;
+
+ HttpStatusCode DoPost() override;
+
+ private:
+ const ServerConfig& server_config_;
+};
+
} // namespace cuttlefish
diff --git a/host/frontend/webrtc_operator/constants/signaling_constants.h b/host/frontend/webrtc_operator/constants/signaling_constants.h
index ecd3a3d..e03a8a8 100644
--- a/host/frontend/webrtc_operator/constants/signaling_constants.h
+++ b/host/frontend/webrtc_operator/constants/signaling_constants.h
@@ -24,6 +24,8 @@
constexpr auto kClientIdField = "client_id";
constexpr auto kPayloadField = "payload";
constexpr auto kServersField = "ice_servers";
+constexpr auto kClientSecretField = "connection_id";
+constexpr auto kDevicePortField = "device_port";
// These are defined in the IceServer dictionary
constexpr auto kUrlsField = "urls";
constexpr auto kUsernameField = "username";
@@ -38,6 +40,7 @@
constexpr auto kClientMessageType = "client_msg";
constexpr auto kClientDisconnectType = "client_disconnected";
constexpr auto kDeviceMessageType = "device_msg";
+constexpr auto kPollType = "client_poll";
} // namespace webrtc_signaling
} // namespace cuttlefish
diff --git a/host/frontend/webrtc_operator/device_list_handler.cpp b/host/frontend/webrtc_operator/device_list_handler.cpp
index 314feea..c92e8e8 100644
--- a/host/frontend/webrtc_operator/device_list_handler.cpp
+++ b/host/frontend/webrtc_operator/device_list_handler.cpp
@@ -18,12 +18,10 @@
namespace cuttlefish {
DeviceListHandler::DeviceListHandler(struct lws* wsi,
- const DeviceRegistry& registry)
- : WebSocketHandler(wsi), registry_(registry) {}
+ DeviceRegistry& registry)
+ : DynHandler(wsi), registry_(registry) {}
-void DeviceListHandler::OnReceive(const uint8_t* /*msg*/, size_t /*len*/,
- bool /*binary*/) {
- // Ignore the message, just send the reply
+HttpStatusCode DeviceListHandler::DoGet() {
Json::Value reply(Json::ValueType::arrayValue);
for (const auto& id : registry_.ListDeviceIds()) {
@@ -31,18 +29,11 @@
}
Json::StreamWriterBuilder json_factory;
auto replyAsString = Json::writeString(json_factory, reply);
- EnqueueMessage(replyAsString.c_str(), replyAsString.size());
- Close();
+ AppendDataOut(replyAsString);
+ return HttpStatusCode::Ok;
+}
+HttpStatusCode DeviceListHandler::DoPost() {
+ return HttpStatusCode::NotFound;
}
-void DeviceListHandler::OnConnected() {}
-
-void DeviceListHandler::OnClosed() {}
-
-DeviceListHandlerFactory::DeviceListHandlerFactory(const DeviceRegistry& registry)
- : registry_(registry) {}
-
-std::shared_ptr<WebSocketHandler> DeviceListHandlerFactory::Build(struct lws* wsi) {
- return std::shared_ptr<WebSocketHandler>(new DeviceListHandler(wsi, registry_));
-}
} // namespace cuttlefish
diff --git a/host/frontend/webrtc_operator/device_list_handler.h b/host/frontend/webrtc_operator/device_list_handler.h
index 99d1f9c..2dc8406 100644
--- a/host/frontend/webrtc_operator/device_list_handler.h
+++ b/host/frontend/webrtc_operator/device_list_handler.h
@@ -25,24 +25,15 @@
namespace cuttlefish {
-class DeviceListHandler : public WebSocketHandler {
+class DeviceListHandler : public DynHandler {
public:
- DeviceListHandler(struct lws* wsi, const DeviceRegistry& registry);
+ DeviceListHandler(struct lws* wsi, DeviceRegistry& registry);
- void OnReceive(const uint8_t* msg, size_t len, bool binary) override;
- void OnConnected() override;
- void OnClosed() override;
+ HttpStatusCode DoGet() override;
+ HttpStatusCode DoPost() override;
private:
- const DeviceRegistry& registry_;
+ DeviceRegistry& registry_;
};
-class DeviceListHandlerFactory : public WebSocketHandlerFactory {
- public:
- DeviceListHandlerFactory(const DeviceRegistry& registry);
- std::shared_ptr<WebSocketHandler> Build(struct lws* wsi) override;
-
- private:
- const DeviceRegistry& registry_;
-};
} // namespace cuttlefish
diff --git a/host/frontend/webrtc_operator/server.cpp b/host/frontend/webrtc_operator/server.cpp
index c1f08f7..65295cf 100644
--- a/host/frontend/webrtc_operator/server.cpp
+++ b/host/frontend/webrtc_operator/server.cpp
@@ -41,7 +41,11 @@
constexpr auto kRegisterDeviceUriPath = "/register_device";
constexpr auto kConnectClientUriPath = "/connect_client";
-constexpr auto kListDevicesUriPath = "/list_devices";
+constexpr auto kListDevicesUriPath = "/devices";
+const constexpr auto kInfraConfigPath = "/infra_config";
+const constexpr auto kConnectPath = "/connect";
+const constexpr auto kForwardPath = "/forward";
+const constexpr auto kPollPath = "/poll_messages";
} // namespace
@@ -50,23 +54,59 @@
::gflags::ParseCommandLineFlags(&argc, &argv, true);
cuttlefish::DeviceRegistry device_registry;
+ cuttlefish::PollConnectionStore poll_store;
cuttlefish::ServerConfig server_config({FLAGS_stun_server});
- cuttlefish::WebSocketServer wss(
- "webrtc-operator", FLAGS_certs_dir, FLAGS_assets_dir, FLAGS_http_server_port);
+ cuttlefish::WebSocketServer wss =
+ FLAGS_use_secure_http
+ ? cuttlefish::WebSocketServer("webrtc-operator", FLAGS_certs_dir,
+ FLAGS_assets_dir,
+ FLAGS_http_server_port)
+ : cuttlefish::WebSocketServer("webrtc-operator", FLAGS_assets_dir,
+ FLAGS_http_server_port);
+ // Device list endpoint
+ wss.RegisterDynHandlerFactory(
+ kListDevicesUriPath, [&device_registry](struct lws* wsi) {
+ return std::unique_ptr<cuttlefish::DynHandler>(
+ new cuttlefish::DeviceListHandler(wsi, device_registry));
+ });
+
+ // Websocket signaling endpoints
auto device_handler_factory_p =
std::unique_ptr<cuttlefish::WebSocketHandlerFactory>(
- new cuttlefish::DeviceHandlerFactory(&device_registry, server_config));
- wss.RegisterHandlerFactory(kRegisterDeviceUriPath, std::move(device_handler_factory_p));
+ new cuttlefish::DeviceHandlerFactory(&device_registry,
+ server_config));
+ wss.RegisterHandlerFactory(kRegisterDeviceUriPath,
+ std::move(device_handler_factory_p));
auto client_handler_factory_p =
std::unique_ptr<cuttlefish::WebSocketHandlerFactory>(
- new cuttlefish::ClientHandlerFactory(&device_registry, server_config));
- wss.RegisterHandlerFactory(kConnectClientUriPath, std::move(client_handler_factory_p));
- auto device_list_handler_factory_p =
- std::unique_ptr<cuttlefish::WebSocketHandlerFactory>(
- new cuttlefish::DeviceListHandlerFactory(device_registry));
- wss.RegisterHandlerFactory(kListDevicesUriPath, std::move(device_list_handler_factory_p));
+ new cuttlefish::ClientWSHandlerFactory(&device_registry,
+ server_config));
+ wss.RegisterHandlerFactory(kConnectClientUriPath,
+ std::move(client_handler_factory_p));
+
+ // Polling signaling endpoints
+ wss.RegisterDynHandlerFactory(
+ kInfraConfigPath, [&server_config](struct lws* wsi) {
+ return std::unique_ptr<cuttlefish::DynHandler>(
+ new cuttlefish::ConfigHandler(wsi, server_config));
+ });
+ wss.RegisterDynHandlerFactory(
+ kConnectPath, [&device_registry, &poll_store](struct lws* wsi) {
+ return std::unique_ptr<cuttlefish::DynHandler>(
+ new cuttlefish::ConnectHandler(wsi, &device_registry, &poll_store));
+ });
+ wss.RegisterDynHandlerFactory(
+ kForwardPath, [&poll_store](struct lws* wsi) {
+ return std::unique_ptr<cuttlefish::DynHandler>(
+ new cuttlefish::ForwardHandler(wsi, &poll_store));
+ });
+ wss.RegisterDynHandlerFactory(
+ kPollPath, [&poll_store](struct lws* wsi) {
+ return std::unique_ptr<cuttlefish::DynHandler>(
+ new cuttlefish::PollHandler(wsi, &poll_store));
+ });
wss.Serve();
return 0;
diff --git a/host/libs/allocd/Android.bp b/host/libs/allocd/Android.bp
index 3c52507..c624817 100644
--- a/host/libs/allocd/Android.bp
+++ b/host/libs/allocd/Android.bp
@@ -41,6 +41,7 @@
"resource.cpp",
],
shared_libs: [
+ "libext2_blkid",
"libbase",
"libcuttlefish_fs",
"libcuttlefish_utils",
@@ -60,6 +61,7 @@
"test/client.cpp",
],
shared_libs: [
+ "libext2_blkid",
"libbase",
"libcuttlefish_allocd_utils",
"libcuttlefish_fs",
diff --git a/host/libs/audio_connector/server.cpp b/host/libs/audio_connector/server.cpp
index c153df9..4f0570c 100644
--- a/host/libs/audio_connector/server.cpp
+++ b/host/libs/audio_connector/server.cpp
@@ -335,7 +335,9 @@
};
std::vector<uint8_t> buffer(sizeof(vio_status) + size, 0);
std::memcpy(buffer.data(), &vio_status, sizeof(vio_status));
- std::memcpy(buffer.data() + sizeof(vio_status), data, size);
+ if (data) {
+ std::memcpy(buffer.data() + sizeof(vio_status), data, size);
+ }
auto status_sent = control_socket_->Send(buffer.data(), buffer.size(), 0);
if (status_sent < sizeof(vio_status) + size) {
LOG(ERROR) << "Failed to send entire command status: "
diff --git a/host/libs/config/Android.bp b/host/libs/config/Android.bp
index 3697f4e..b3d0efb 100644
--- a/host/libs/config/Android.bp
+++ b/host/libs/config/Android.bp
@@ -21,10 +21,12 @@
name: "libcuttlefish_host_config",
srcs: [
"bootconfig_args.cpp",
+ "config_flag.cpp",
"custom_actions.cpp",
"cuttlefish_config.cpp",
"cuttlefish_config_instance.cpp",
"data_image.cpp",
+ "feature.cpp",
"fetcher_config.cpp",
"host_tools_version.cpp",
"kernel_args.cpp",
@@ -32,9 +34,11 @@
"logging.cpp",
],
shared_libs: [
+ "libext2_blkid",
"libcuttlefish_fs",
"libcuttlefish_utils",
"libbase",
+ "libfruit",
"libgflags",
"libjsoncpp",
"libz",
diff --git a/host/libs/config/adb/Android.bp b/host/libs/config/adb/Android.bp
new file mode 100644
index 0000000..c1b74de
--- /dev/null
+++ b/host/libs/config/adb/Android.bp
@@ -0,0 +1,64 @@
+//
+// Copyright (C) 2017 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package {
+ default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+cc_library_static {
+ name: "libcuttlefish_host_config_adb",
+ srcs: [
+ "config.cpp",
+ "data.cpp",
+ "flags.cpp",
+ "launch.cpp",
+ "strings.cpp",
+ ],
+ shared_libs: [
+ "libcuttlefish_fs",
+ "libcuttlefish_utils",
+ "libbase",
+ "libfruit",
+ "libgflags",
+ "libjsoncpp",
+ "libz",
+ ],
+ defaults: ["cuttlefish_host"],
+}
+
+cc_test_host {
+ name: "libcuttlefish_host_config_adb_test",
+ srcs: [
+ "test.cpp",
+ ],
+ static_libs: [
+ "libbase",
+ "libcuttlefish_fs",
+ "libcuttlefish_host_config",
+ "libcuttlefish_host_config_adb",
+ "libcuttlefish_utils",
+ ],
+ shared_libs: [
+ "libext2_blkid",
+ "libgflags",
+ "libfruit",
+ "libjsoncpp",
+ "liblog",
+ ],
+ defaults: ["cuttlefish_host"],
+ test_options: {
+ unit_test: true,
+ },
+}
diff --git a/host/libs/config/adb/adb.h b/host/libs/config/adb/adb.h
new file mode 100644
index 0000000..08b1549
--- /dev/null
+++ b/host/libs/config/adb/adb.h
@@ -0,0 +1,63 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#pragma once
+
+#include <fruit/fruit.h>
+#include <set>
+
+#include "host/libs/config/command_source.h"
+#include "host/libs/config/config_flag.h"
+#include "host/libs/config/config_fragment.h"
+#include "host/libs/config/cuttlefish_config.h"
+#include "host/libs/config/feature.h"
+#include "host/libs/config/kernel_log_pipe_provider.h"
+
+namespace cuttlefish {
+
+enum class AdbMode {
+ VsockTunnel,
+ VsockHalfTunnel,
+ NativeVsock,
+ Unknown,
+};
+
+AdbMode StringToAdbMode(const std::string& mode);
+std::string AdbModeToString(AdbMode mode);
+
+class AdbConfig {
+ public:
+ virtual ~AdbConfig() = default;
+ virtual const std::set<AdbMode>& Modes() const = 0;
+ virtual bool SetModes(const std::set<AdbMode>&) = 0;
+ virtual bool SetModes(std::set<AdbMode>&&) = 0;
+
+ virtual bool RunConnector() const = 0;
+ virtual bool SetRunConnector(bool) = 0;
+};
+
+class AdbConfigFragment : public ConfigFragment {};
+class AdbConfigFlag : public FlagFeature {};
+
+fruit::Component<AdbConfig> AdbConfigComponent();
+fruit::Component<fruit::Required<AdbConfig, ConfigFlag>, AdbConfigFlag>
+AdbConfigFlagComponent();
+fruit::Component<fruit::Required<AdbConfig>, AdbConfigFragment>
+AdbConfigFragmentComponent();
+fruit::Component<fruit::Required<KernelLogPipeProvider, const AdbConfig,
+ const CuttlefishConfig::InstanceSpecific>>
+LaunchAdbComponent();
+
+} // namespace cuttlefish
diff --git a/host/libs/config/adb/config.cpp b/host/libs/config/adb/config.cpp
new file mode 100644
index 0000000..6fc6378
--- /dev/null
+++ b/host/libs/config/adb/config.cpp
@@ -0,0 +1,86 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#include "host/libs/config/adb/adb.h"
+
+#include <android-base/logging.h>
+#include <fruit/fruit.h>
+#include <json/json.h>
+
+#include "host/libs/config/config_fragment.h"
+
+namespace cuttlefish {
+namespace {
+
+class AdbConfigFragmentImpl : public AdbConfigFragment {
+ public:
+ INJECT(AdbConfigFragmentImpl(AdbConfig& config)) : config_(config) {}
+
+ std::string Name() const override { return "AdbConfigFragmentImpl"; }
+
+ Json::Value Serialize() const override {
+ Json::Value json;
+ json[kMode] = Json::Value(Json::arrayValue);
+ for (const auto& mode : config_.Modes()) {
+ json[kMode].append(AdbModeToString(mode));
+ }
+ json[kConnectorEnabled] = config_.RunConnector();
+ return json;
+ }
+ bool Deserialize(const Json::Value& json) override {
+ if (!json.isMember(kMode) || json[kMode].type() != Json::arrayValue) {
+ LOG(ERROR) << "Invalid value for " << kMode;
+ return false;
+ }
+ std::set<AdbMode> modes;
+ for (auto& mode : json[kMode]) {
+ if (mode.type() != Json::stringValue) {
+ LOG(ERROR) << "Invalid mode type" << mode;
+ return false;
+ }
+ modes.insert(StringToAdbMode(mode.asString()));
+ }
+ if (!config_.SetModes(std::move(modes))) {
+ LOG(ERROR) << "Failed to set adb modes";
+ return false;
+ }
+
+ if (!json.isMember(kConnectorEnabled) ||
+ json[kConnectorEnabled].type() != Json::booleanValue) {
+ LOG(ERROR) << "Invalid value for " << kConnectorEnabled;
+ return false;
+ }
+ if (!config_.SetRunConnector(json[kConnectorEnabled].asBool())) {
+ LOG(ERROR) << "Failed to set whether to run the adb connector";
+ }
+ return true;
+ }
+
+ private:
+ static constexpr char kMode[] = "mode";
+ static constexpr char kConnectorEnabled[] = "connector_enabled";
+ AdbConfig& config_;
+};
+
+} // namespace
+
+fruit::Component<fruit::Required<AdbConfig>, AdbConfigFragment>
+AdbConfigFragmentComponent() {
+ return fruit::createComponent()
+ .bind<AdbConfigFragment, AdbConfigFragmentImpl>()
+ .addMultibinding<ConfigFragment, AdbConfigFragment>();
+}
+
+} // namespace cuttlefish
diff --git a/host/libs/config/adb/data.cpp b/host/libs/config/adb/data.cpp
new file mode 100644
index 0000000..81db3e9
--- /dev/null
+++ b/host/libs/config/adb/data.cpp
@@ -0,0 +1,53 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#include "host/libs/config/adb/adb.h"
+
+#include <fruit/fruit.h>
+#include <set>
+
+namespace cuttlefish {
+namespace {}
+
+class AdbConfigImpl : public AdbConfig {
+ public:
+ INJECT(AdbConfigImpl()) {}
+
+ const std::set<AdbMode>& Modes() const override { return modes_; }
+ bool SetModes(const std::set<AdbMode>& modes) override {
+ modes_ = modes;
+ return true;
+ }
+ bool SetModes(std::set<AdbMode>&& modes) override {
+ modes_ = std::move(modes);
+ return true;
+ }
+
+ bool RunConnector() const override { return run_connector_; }
+ bool SetRunConnector(bool run) override {
+ run_connector_ = run;
+ return true;
+ }
+
+ private:
+ std::set<AdbMode> modes_;
+ bool run_connector_;
+};
+
+fruit::Component<AdbConfig> AdbConfigComponent() {
+ return fruit::createComponent().bind<AdbConfig, AdbConfigImpl>();
+}
+
+} // namespace cuttlefish
diff --git a/host/libs/config/adb/flags.cpp b/host/libs/config/adb/flags.cpp
new file mode 100644
index 0000000..56451f7
--- /dev/null
+++ b/host/libs/config/adb/flags.cpp
@@ -0,0 +1,106 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#include "host/libs/config/adb/adb.h"
+
+#include <android-base/strings.h>
+
+#include "common/libs/utils/flag_parser.h"
+#include "host/libs/config/config_flag.h"
+#include "host/libs/config/feature.h"
+
+namespace cuttlefish {
+namespace {
+
+class AdbConfigFlagImpl : public AdbConfigFlag {
+ public:
+ INJECT(AdbConfigFlagImpl(AdbConfig& config, ConfigFlag& config_flag))
+ : config_(config), config_flag_(config_flag) {
+ mode_flag_ = GflagsCompatFlag("adb_mode").Help(mode_help);
+ mode_flag_.Getter([this]() {
+ std::stringstream modes;
+ for (const auto& mode : config_.Modes()) {
+ modes << "," << AdbModeToString(mode);
+ }
+ return modes.str().substr(1); // First comma
+ });
+ mode_flag_.Setter([this](const FlagMatch& match) {
+ // TODO(schuffelen): Error on unknown types?
+ std::set<AdbMode> modes;
+ for (auto& mode : android::base::Split(match.value, ",")) {
+ modes.insert(StringToAdbMode(mode));
+ }
+ return config_.SetModes(modes);
+ });
+ }
+
+ std::string Name() const override { return "AdbConfigFlagImpl"; }
+
+ std::unordered_set<FlagFeature*> Dependencies() const override {
+ return {static_cast<FlagFeature*>(&config_flag_)};
+ }
+
+ bool Process(std::vector<std::string>& args) override {
+ // Defaults
+ config_.SetModes({AdbMode::VsockHalfTunnel});
+ bool run_adb_connector = !IsRunningInContainer();
+ Flag run_flag = GflagsCompatFlag("run_adb_connector", run_adb_connector);
+ if (!ParseFlags({run_flag, mode_flag_}, args)) {
+ LOG(ERROR) << "Failed to parse adb config flags";
+ return false;
+ }
+ config_.SetRunConnector(run_adb_connector);
+
+ auto adb_modes_check = config_.Modes();
+ adb_modes_check.erase(AdbMode::Unknown);
+ if (adb_modes_check.size() < 1) {
+ LOG(INFO) << "ADB not enabled";
+ }
+
+ return true;
+ }
+ bool WriteGflagsCompatHelpXml(std::ostream& out) const override {
+ bool run = config_.RunConnector();
+ Flag run_flag = GflagsCompatFlag("run_adb_connector", run).Help(run_help);
+ return WriteGflagsCompatXml({run_flag, mode_flag_}, out);
+ }
+
+ private:
+ static constexpr char run_help[] =
+ "Maintain adb connection by sending 'adb connect' commands to the "
+ "server. Only relevant with -adb_mode=tunnel or vsock_tunnel.";
+ static constexpr char mode_help[] =
+ "Mode for ADB connection."
+ "'vsock_tunnel' for a TCP connection tunneled through vsock, "
+ "'native_vsock' for a direct connection to the guest ADB over "
+ "vsock, 'vsock_half_tunnel' for a TCP connection forwarded to "
+ "the guest ADB server, or a comma separated list of types as in "
+ "'native_vsock,vsock_half_tunnel'";
+
+ AdbConfig& config_;
+ ConfigFlag& config_flag_;
+ Flag mode_flag_;
+};
+
+} // namespace
+
+fruit::Component<fruit::Required<AdbConfig, ConfigFlag>, AdbConfigFlag>
+AdbConfigFlagComponent() {
+ return fruit::createComponent()
+ .bind<AdbConfigFlag, AdbConfigFlagImpl>()
+ .addMultibinding<FlagFeature, AdbConfigFlag>();
+}
+
+} // namespace cuttlefish
diff --git a/host/libs/config/adb/launch.cpp b/host/libs/config/adb/launch.cpp
new file mode 100644
index 0000000..65f439b
--- /dev/null
+++ b/host/libs/config/adb/launch.cpp
@@ -0,0 +1,208 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#include "host/libs/config/adb/adb.h"
+
+#include "host/libs/config/cuttlefish_config.h"
+#include "host/libs/config/known_paths.h"
+
+namespace cuttlefish {
+namespace {
+
+class AdbHelper {
+ public:
+ INJECT(AdbHelper(const CuttlefishConfig::InstanceSpecific& instance,
+ const AdbConfig& config))
+ : instance_(instance), config_(config) {}
+
+ bool ModeEnabled(const AdbMode& mode) const {
+ return config_.Modes().count(mode) > 0;
+ }
+
+ std::string ConnectorTcpArg() const {
+ return "0.0.0.0:" + std::to_string(instance_.adb_host_port());
+ }
+
+ std::string ConnectorVsockArg() const {
+ return "vsock:" + std::to_string(instance_.vsock_guest_cid()) + ":5555";
+ }
+
+ bool VsockTunnelEnabled() const {
+ return instance_.vsock_guest_cid() > 2 && ModeEnabled(AdbMode::VsockTunnel);
+ }
+
+ bool VsockHalfTunnelEnabled() const {
+ return instance_.vsock_guest_cid() > 2 &&
+ ModeEnabled(AdbMode::VsockHalfTunnel);
+ }
+
+ bool TcpConnectorEnabled() const {
+ bool vsock_tunnel = VsockTunnelEnabled();
+ bool vsock_half_tunnel = VsockHalfTunnelEnabled();
+ return config_.RunConnector() && (vsock_tunnel || vsock_half_tunnel);
+ }
+
+ bool VsockConnectorEnabled() const {
+ return config_.RunConnector() && ModeEnabled(AdbMode::NativeVsock);
+ }
+
+ private:
+ const CuttlefishConfig::InstanceSpecific& instance_;
+ const AdbConfig& config_;
+};
+
+class AdbConnector : public CommandSource {
+ public:
+ INJECT(AdbConnector(const AdbHelper& helper)) : helper_(helper) {}
+
+ // CommandSource
+ std::vector<Command> Commands() override {
+ Command console_forwarder_cmd(ConsoleForwarderBinary());
+ Command adb_connector(AdbConnectorBinary());
+ std::set<std::string> addresses;
+
+ if (helper_.TcpConnectorEnabled()) {
+ addresses.insert(helper_.ConnectorTcpArg());
+ }
+ if (helper_.VsockConnectorEnabled()) {
+ addresses.insert(helper_.ConnectorVsockArg());
+ }
+
+ if (addresses.size() == 0) {
+ return {};
+ }
+ std::string address_arg = "--addresses=";
+ for (auto& arg : addresses) {
+ address_arg += arg + ",";
+ }
+ address_arg.pop_back();
+ adb_connector.AddParameter(address_arg);
+ std::vector<Command> commands;
+ commands.emplace_back(std::move(adb_connector));
+ return std::move(commands);
+ }
+
+ // SetupFeature
+ std::string Name() const override { return "AdbConnector"; }
+ bool Enabled() const override {
+ return helper_.TcpConnectorEnabled() || helper_.VsockConnectorEnabled();
+ }
+
+ private:
+ std::unordered_set<SetupFeature*> Dependencies() const override { return {}; }
+ bool Setup() override { return true; }
+
+ const AdbHelper& helper_;
+};
+
+class SocketVsockProxy : public CommandSource {
+ public:
+ INJECT(SocketVsockProxy(const AdbHelper& helper,
+ const CuttlefishConfig::InstanceSpecific& instance,
+ KernelLogPipeProvider& log_pipe_provider))
+ : helper_(helper),
+ instance_(instance),
+ log_pipe_provider_(log_pipe_provider) {}
+
+ // CommandSource
+ std::vector<Command> Commands() override {
+ std::vector<Command> commands;
+ if (helper_.VsockTunnelEnabled()) {
+ Command adb_tunnel(SocketVsockProxyBinary());
+ adb_tunnel.AddParameter("-adbd_events_fd=", kernel_log_pipe_);
+ /**
+ * This socket_vsock_proxy (a.k.a. sv proxy) runs on the host. It assumes
+ * that another sv proxy runs inside the guest. see:
+ * shared/config/init.vendor.rc The sv proxy in the guest exposes
+ * vsock:cid:6520 across the cuttlefish instances in multi-tenancy. cid is
+ * different per instance.
+ *
+ * This host sv proxy should cooperate with the guest sv proxy. Thus, one
+ * end of the tunnel is vsock:cid:6520 regardless of instance number.
+ * Another end faces the host adb daemon via tcp. Thus, the server type is
+ * tcp here. The tcp port differs from instance to instance, and is
+ * instance.adb_host_port()
+ *
+ */
+ adb_tunnel.AddParameter("--server=tcp");
+ adb_tunnel.AddParameter("--vsock_port=6520");
+ adb_tunnel.AddParameter("--server_fd=", tcp_server_);
+ adb_tunnel.AddParameter("--vsock_cid=", instance_.vsock_guest_cid());
+ commands.emplace_back(std::move(adb_tunnel));
+ }
+ if (helper_.VsockHalfTunnelEnabled()) {
+ Command adb_tunnel(SocketVsockProxyBinary());
+ adb_tunnel.AddParameter("-adbd_events_fd=", kernel_log_pipe_);
+ /*
+ * This socket_vsock_proxy (a.k.a. sv proxy) runs on the host, and
+ * cooperates with the adbd inside the guest. See this file:
+ * shared/device.mk, especially the line says "persist.adb.tcp.port="
+ *
+ * The guest adbd is listening on vsock:cid:5555 across cuttlefish
+ * instances. Sv proxy faces the host adb daemon via tcp. The server type
+ * should be therefore tcp, and the port should differ from instance to
+ * instance and be equal to instance.adb_host_port()
+ */
+ adb_tunnel.AddParameter("--server=tcp");
+ adb_tunnel.AddParameter("--vsock_port=", 5555);
+ adb_tunnel.AddParameter("--server_fd=", tcp_server_);
+ adb_tunnel.AddParameter("--vsock_cid=", instance_.vsock_guest_cid());
+ commands.emplace_back(std::move(adb_tunnel));
+ }
+ return commands;
+ }
+
+ // SetupFeature
+ std::string Name() const override { return "SocketVsockProxy"; }
+ bool Enabled() const override {
+ return helper_.VsockTunnelEnabled() || helper_.VsockHalfTunnelEnabled();
+ }
+
+ private:
+ std::unordered_set<SetupFeature*> Dependencies() const override {
+ return {static_cast<SetupFeature*>(&log_pipe_provider_)};
+ }
+ bool Setup() override {
+ tcp_server_ =
+ SharedFD::SocketLocalServer(instance_.adb_host_port(), SOCK_STREAM);
+ if (!tcp_server_->IsOpen()) {
+ LOG(ERROR) << "Unable to create socket_vsock_proxy server socket: "
+ << tcp_server_->StrError();
+ return false;
+ }
+ kernel_log_pipe_ = log_pipe_provider_.KernelLogPipe();
+ return true;
+ }
+
+ const AdbHelper& helper_;
+ const CuttlefishConfig::InstanceSpecific& instance_;
+ KernelLogPipeProvider& log_pipe_provider_;
+ SharedFD kernel_log_pipe_;
+ SharedFD tcp_server_;
+};
+
+} // namespace
+
+fruit::Component<fruit::Required<KernelLogPipeProvider, const AdbConfig,
+ const CuttlefishConfig::InstanceSpecific>>
+LaunchAdbComponent() {
+ return fruit::createComponent()
+ .addMultibinding<CommandSource, AdbConnector>()
+ .addMultibinding<CommandSource, SocketVsockProxy>()
+ .addMultibinding<SetupFeature, AdbConnector>()
+ .addMultibinding<SetupFeature, SocketVsockProxy>();
+}
+
+} // namespace cuttlefish
diff --git a/host/libs/config/adb/strings.cpp b/host/libs/config/adb/strings.cpp
new file mode 100644
index 0000000..4de9e23
--- /dev/null
+++ b/host/libs/config/adb/strings.cpp
@@ -0,0 +1,51 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#include "host/libs/config/adb/adb.h"
+
+#include <algorithm>
+#include <string>
+
+namespace cuttlefish {
+
+AdbMode StringToAdbMode(const std::string& mode_cased) {
+ std::string mode = mode_cased;
+ std::transform(mode.begin(), mode.end(), mode.begin(), ::tolower);
+ if (mode == "vsock_tunnel") {
+ return AdbMode::VsockTunnel;
+ } else if (mode == "vsock_half_tunnel") {
+ return AdbMode::VsockHalfTunnel;
+ } else if (mode == "native_vsock") {
+ return AdbMode::NativeVsock;
+ } else {
+ return AdbMode::Unknown;
+ }
+}
+
+std::string AdbModeToString(AdbMode mode) {
+ switch (mode) {
+ case AdbMode::VsockTunnel:
+ return "vsock_tunnel";
+ case AdbMode::VsockHalfTunnel:
+ return "vsock_half_tunnel";
+ case AdbMode::NativeVsock:
+ return "native_vsock";
+ case AdbMode::Unknown: // fall through
+ default:
+ return "unknown";
+ }
+}
+
+} // namespace cuttlefish
diff --git a/host/libs/config/adb/test.cpp b/host/libs/config/adb/test.cpp
new file mode 100644
index 0000000..3250568
--- /dev/null
+++ b/host/libs/config/adb/test.cpp
@@ -0,0 +1,76 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <fruit/fruit.h>
+#include <gtest/gtest.h>
+#include <host/libs/config/adb/adb.h>
+
+#include <string>
+
+#include "host/libs/config/feature.h"
+
+namespace cuttlefish {
+
+struct TestData {
+ INJECT(TestData(AdbConfig& config, AdbConfigFragment& fragment))
+ : config(config), fragment(fragment) {}
+
+ AdbConfig& config;
+ AdbConfigFragment& fragment;
+};
+
+fruit::Component<TestData> TestComponent() {
+ return fruit::createComponent()
+ .install(AdbConfigComponent)
+ .install(AdbConfigFlagComponent)
+ .install(AdbConfigFragmentComponent)
+ .install(ConfigFlagPlaceholder);
+}
+
+TEST(AdbConfigTest, SetFromFlags) {
+ fruit::Injector<TestData> injector(TestComponent);
+ TestData& data = injector.get<TestData&>();
+ std::vector<std::string> args = {
+ "--adb_mode=vsock_tunnel,vsock_half_tunnel,native_vsock,unknown",
+ "--run_adb_connector=false",
+ };
+ auto flags = injector.getMultibindings<FlagFeature>();
+ auto processed = FlagFeature::ProcessFlags(flags, args);
+ ASSERT_TRUE(processed.ok()) << processed.error();
+ ASSERT_TRUE(args.empty());
+
+ std::set<AdbMode> modes = {AdbMode::VsockTunnel, AdbMode::VsockHalfTunnel,
+ AdbMode::NativeVsock, AdbMode::Unknown};
+ ASSERT_EQ(data.config.Modes(), modes);
+ ASSERT_FALSE(data.config.RunConnector());
+}
+
+TEST(AdbConfigTest, SerializeDeserialize) {
+ fruit::Injector<TestData> injector1(TestComponent);
+ TestData& data1 = injector1.get<TestData&>();
+ ASSERT_TRUE(
+ data1.config.SetModes({AdbMode::VsockTunnel, AdbMode::VsockHalfTunnel,
+ AdbMode::NativeVsock, AdbMode::Unknown}));
+ ASSERT_TRUE(data1.config.SetRunConnector(false));
+
+ fruit::Injector<TestData> injector2(TestComponent);
+ TestData& data2 = injector2.get<TestData&>();
+ ASSERT_TRUE(data2.fragment.Deserialize(data1.fragment.Serialize()));
+ ASSERT_EQ(data1.config.Modes(), data2.config.Modes());
+ ASSERT_EQ(data1.config.RunConnector(), data2.config.RunConnector());
+}
+
+} // namespace cuttlefish
diff --git a/host/libs/config/bootconfig_args.cpp b/host/libs/config/bootconfig_args.cpp
index 1f926eb..00673f6 100644
--- a/host/libs/config/bootconfig_args.cpp
+++ b/host/libs/config/bootconfig_args.cpp
@@ -24,6 +24,7 @@
#include "common/libs/utils/environment.h"
#include "common/libs/utils/files.h"
#include "host/libs/config/cuttlefish_config.h"
+#include "host/libs/config/known_paths.h"
#include "host/libs/vm_manager/crosvm_manager.h"
#include "host/libs/vm_manager/qemu_manager.h"
#include "host/libs/vm_manager/vm_manager.h"
@@ -47,15 +48,6 @@
return os.str();
}
-std::string mac_to_str(const std::array<unsigned char, 6>& mac) {
- std::ostringstream stream;
- stream << std::hex << (int)mac[0];
- for (int i = 1; i < 6; i++) {
- stream << ":" << std::hex << (int)mac[i];
- }
- return stream.str();
-}
-
// TODO(schuffelen): Move more of this into host/libs/vm_manager, as a
// substitute for the vm_manager comparisons.
std::vector<std::string> VmManagerBootconfig(const CuttlefishConfig& config) {
@@ -86,7 +78,7 @@
auto vmm = vm_manager::GetVmManager(config.vm_manager(), config.target_arch());
bootconfig_args.push_back(
vmm->ConfigureBootDevices(instance.virtual_disk_paths().size()));
- AppendVector(&bootconfig_args, vmm->ConfigureGpuMode(config.gpu_mode()));
+ AppendVector(&bootconfig_args, vmm->ConfigureGraphics(config));
bootconfig_args.push_back(
concat("androidboot.serialno=", instance.serial_number()));
@@ -108,6 +100,11 @@
instance.tombstone_receiver_port()));
}
+ if (instance.confui_host_vsock_port()) {
+ bootconfig_args.push_back(concat("androidboot.vsock_confirmationui_port=",
+ instance.confui_host_vsock_port()));
+ }
+
if (instance.config_server_port()) {
bootconfig_args.push_back(
concat("androidboot.cuttlefish_config_server_port=",
@@ -126,7 +123,7 @@
if (config.enable_vehicle_hal_grpc_server() &&
instance.vehicle_hal_server_port() &&
- FileExists(config.vehicle_hal_grpc_server_binary())) {
+ FileExists(VehicleHalGrpcServerBinary())) {
constexpr int vehicle_hal_server_cid = 2;
bootconfig_args.push_back(concat(
"androidboot.vendor.vehiclehal.server.cid=", vehicle_hal_server_cid));
@@ -144,11 +141,6 @@
instance.audiocontrol_server_port()));
}
- if (instance.frames_server_port()) {
- bootconfig_args.push_back(concat("androidboot.vsock_frames_port=",
- instance.frames_server_port()));
- }
-
if (instance.camera_server_port()) {
bootconfig_args.push_back(concat("androidboot.vsock_camera_port=",
instance.camera_server_port()));
@@ -162,11 +154,11 @@
instance.modem_simulator_ports()));
}
- // TODO(b/158131610): Set this in crosvm instead
- bootconfig_args.push_back(concat("androidboot.wifi_mac_address=",
- mac_to_str(instance.wifi_mac_address())));
+ bootconfig_args.push_back(concat("androidboot.fstab_suffix=",
+ config.userdata_format()));
- bootconfig_args.push_back("androidboot.verifiedbootstate=orange");
+ bootconfig_args.push_back(
+ concat("androidboot.wifi_mac_prefix=", instance.wifi_mac_prefix()));
// Non-native architecture implies a significantly slower execution speed, so
// set a large timeout multiplier.
@@ -174,7 +166,17 @@
bootconfig_args.push_back("androidboot.hw_timeout_multiplier=50");
}
- // TODO(b/173815685): Create an extra_bootconfig flag and add it to bootconfig
+ // TODO(b/217564326): improve this checks for a hypervisor in the VM.
+ if (config.target_arch() == Arch::X86 ||
+ config.target_arch() == Arch::X86_64) {
+ bootconfig_args.push_back(
+ concat("androidboot.hypervisor.version=cf-", config.vm_manager()));
+ bootconfig_args.push_back("androidboot.hypervisor.vm.supported=1");
+ bootconfig_args.push_back(
+ "androidboot.hypervisor.protected_vm.supported=0");
+ }
+
+ AppendVector(&bootconfig_args, config.extra_bootconfig_args());
return bootconfig_args;
}
diff --git a/guest/hals/audio/Android.bp b/host/libs/config/command_source.h
similarity index 62%
rename from guest/hals/audio/Android.bp
rename to host/libs/config/command_source.h
index c6faae7..c4e3b23 100644
--- a/guest/hals/audio/Android.bp
+++ b/host/libs/config/command_source.h
@@ -1,3 +1,4 @@
+//
// Copyright (C) 2019 The Android Open Source Project
//
// Licensed under the Apache License, Version 2.0 (the "License");
@@ -12,16 +13,20 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-package {
- default_applicable_licenses: ["Android-Apache-2.0"],
-}
+#pragma once
-cc_library_shared {
- name: "audio.primary.cutf",
- relative_install_path: "hw",
- defaults: ["cuttlefish_guest_only"],
- vendor: true,
- srcs: ["audio_hw.c"],
- cflags: ["-Wno-unused-parameter"],
- shared_libs: ["libcutils", "libhardware", "liblog", "libtinyalsa"],
-}
+#include <fruit/fruit.h>
+#include <vector>
+
+#include "common/libs/utils/subprocess.h"
+#include "host/libs/config/feature.h"
+
+namespace cuttlefish {
+
+class CommandSource : public virtual SetupFeature {
+ public:
+ virtual ~CommandSource() = default;
+ virtual std::vector<Command> Commands() = 0;
+};
+
+} // namespace cuttlefish
diff --git a/host/libs/config/config_flag.cpp b/host/libs/config/config_flag.cpp
new file mode 100644
index 0000000..d7b5f00
--- /dev/null
+++ b/host/libs/config/config_flag.cpp
@@ -0,0 +1,242 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "host/libs/config/config_flag.h"
+
+#include <android-base/logging.h>
+#include <android-base/strings.h>
+#include <gflags/gflags.h>
+#include <json/json.h>
+#include <fstream>
+#include <set>
+#include <string>
+
+#include "common/libs/utils/files.h"
+#include "common/libs/utils/flag_parser.h"
+#include "host/libs/config/cuttlefish_config.h"
+
+// To support other files that use this from gflags.
+DEFINE_string(system_image_dir, "", "");
+
+using gflags::FlagSettingMode::SET_FLAGS_DEFAULT;
+
+namespace cuttlefish {
+
+namespace {
+
+class SystemImageDirFlagImpl : public SystemImageDirFlag {
+ public:
+ INJECT(SystemImageDirFlagImpl()) {
+ auto help = "Location of the system partition images.";
+ flag_ = GflagsCompatFlag("system_image_dir", path_).Help(help);
+ }
+ const std::string& Path() override { return path_; }
+
+ std::string Name() const override { return "SystemImageDirFlagImpl"; }
+ std::unordered_set<FlagFeature*> Dependencies() const override { return {}; }
+ bool Process(std::vector<std::string>& args) override {
+ path_ = DefaultGuestImagePath("");
+ if (!flag_.Parse(args)) {
+ return false;
+ }
+ // To support other files that use this from gflags.
+ FLAGS_system_image_dir = path_;
+ gflags::SetCommandLineOptionWithMode("system_image_dir", path_.c_str(),
+ SET_FLAGS_DEFAULT);
+ return true;
+ }
+ bool WriteGflagsCompatHelpXml(std::ostream&) const override {
+ // TODO(schuffelen): Write something here when this is removed from gflags
+ return true;
+ }
+
+ private:
+ std::string path_;
+ Flag flag_;
+};
+
+class ConfigReader : public FlagFeature {
+ public:
+ INJECT(ConfigReader()) = default;
+
+ bool HasConfig(const std::string& name) const {
+ return allowed_config_presets_.count(name) > 0;
+ }
+ const std::set<std::string>& AvailableConfigs() const {
+ return allowed_config_presets_;
+ }
+ std::optional<Json::Value> ReadConfig(const std::string& name) const {
+ auto path =
+ DefaultHostArtifactsPath("etc/cvd_config/cvd_config_" + name + ".json");
+ Json::Value config;
+ Json::CharReaderBuilder builder;
+ std::ifstream ifs(path);
+ std::string errorMessage;
+ if (!Json::parseFromStream(builder, ifs, &config, &errorMessage)) {
+ LOG(ERROR) << "Could not read config file " << path << ": "
+ << errorMessage;
+ return {};
+ }
+ return config;
+ }
+
+ // FlagFeature
+ std::string Name() const override { return "ConfigReader"; }
+ std::unordered_set<FlagFeature*> Dependencies() const override { return {}; }
+ bool Process(std::vector<std::string>&) override {
+ for (const std::string& file :
+ DirectoryContents(DefaultHostArtifactsPath("etc/cvd_config"))) {
+ std::string_view local_file(file);
+ if (android::base::ConsumePrefix(&local_file, "cvd_config_") &&
+ android::base::ConsumeSuffix(&local_file, ".json")) {
+ allowed_config_presets_.emplace(local_file);
+ }
+ }
+ return true;
+ }
+ bool WriteGflagsCompatHelpXml(std::ostream&) const override { return true; }
+
+ private:
+ std::set<std::string> allowed_config_presets_;
+};
+
+class ConfigFlagImpl : public ConfigFlag {
+ public:
+ INJECT(ConfigFlagImpl(ConfigReader& cr, SystemImageDirFlag& s))
+ : config_reader_(cr), system_image_dir_flag_(s) {
+ is_default_ = true;
+ config_ = "phone"; // default value
+ auto help =
+ "Config preset name. Will automatically set flag fields using the "
+ "values from this file of presets. See "
+ "device/google/cuttlefish/shared/config/config_*.json for possible "
+ "values.";
+ auto getter = [this]() { return config_; };
+ auto setter = [this](const FlagMatch& m) { return ChooseConfig(m.value); };
+ flag_ = GflagsCompatFlag("config").Help(help).Getter(getter).Setter(setter);
+ }
+
+ std::string Name() const override { return "ConfigFlagImpl"; }
+ std::unordered_set<FlagFeature*> Dependencies() const override {
+ return {
+ static_cast<FlagFeature*>(&config_reader_),
+ static_cast<FlagFeature*>(&system_image_dir_flag_),
+ };
+ }
+ bool Process(std::vector<std::string>& args) override {
+ if (!flag_.Parse(args)) {
+ LOG(ERROR) << "Failed to parse `--config` flag";
+ return false;
+ }
+
+ if (auto info_cfg = FindAndroidInfoConfig(); is_default_ && info_cfg) {
+ config_ = *info_cfg;
+ }
+ LOG(INFO) << "Launching CVD using --config='" << config_ << "'.";
+ auto config_values = config_reader_.ReadConfig(config_);
+ if (!config_values) {
+ LOG(ERROR) << "Failed to read config for " << config_;
+ return false;
+ }
+ for (const std::string& flag : config_values->getMemberNames()) {
+ std::string value;
+ if (flag == "custom_actions") {
+ Json::StreamWriterBuilder factory;
+ value = Json::writeString(factory, (*config_values)[flag]);
+ } else {
+ value = (*config_values)[flag].asString();
+ }
+ args.insert(args.begin(), "--" + flag + "=" + value);
+ // To avoid the flag forwarder from thinking this song is different from a
+ // default. Should fail silently if the flag doesn't exist.
+ gflags::SetCommandLineOptionWithMode(flag.c_str(), value.c_str(),
+ SET_FLAGS_DEFAULT);
+ }
+ return true;
+ }
+ bool WriteGflagsCompatHelpXml(std::ostream& out) const override {
+ return flag_.WriteGflagsCompatXml(out);
+ }
+
+ private:
+ bool ChooseConfig(const std::string& name) {
+ if (!config_reader_.HasConfig(name)) {
+ LOG(ERROR) << "Invalid --config option '" << name << "'. Valid options: "
+ << android::base::Join(config_reader_.AvailableConfigs(), ",");
+ return false;
+ }
+ config_ = name;
+ is_default_ = false;
+ return true;
+ }
+ std::optional<std::string> FindAndroidInfoConfig() const {
+ auto info_path = system_image_dir_flag_.Path() + "/android-info.txt";
+ if (!FileExists(info_path)) {
+ return {};
+ }
+ std::ifstream ifs{info_path};
+ if (!ifs.is_open()) {
+ return {};
+ }
+ std::string android_info;
+ ifs >> android_info;
+ std::string_view local_android_info(android_info);
+ if (!android::base::ConsumePrefix(&local_android_info, "config=")) {
+ return {};
+ }
+ if (!config_reader_.HasConfig(std::string{local_android_info})) {
+ LOG(WARNING) << info_path << " contains invalid config preset: '"
+ << local_android_info << "'.";
+ return {};
+ }
+ return std::string{local_android_info};
+ }
+
+ ConfigReader& config_reader_;
+ SystemImageDirFlag& system_image_dir_flag_;
+ std::string config_;
+ bool is_default_;
+ Flag flag_;
+};
+
+class ConfigFlagPlaceholderImpl : public ConfigFlag {
+ public:
+ INJECT(ConfigFlagPlaceholderImpl()) {}
+
+ std::string Name() const override { return "ConfigFlagPlaceholderImpl"; }
+ std::unordered_set<FlagFeature*> Dependencies() const override { return {}; }
+ bool Process(std::vector<std::string>&) override { return true; }
+ bool WriteGflagsCompatHelpXml(std::ostream&) const override { return true; }
+};
+
+} // namespace
+
+fruit::Component<SystemImageDirFlag, ConfigFlag> ConfigFlagComponent() {
+ return fruit::createComponent()
+ .addMultibinding<FlagFeature, ConfigReader>()
+ .bind<ConfigFlag, ConfigFlagImpl>()
+ .addMultibinding<FlagFeature, ConfigFlag>()
+ .bind<SystemImageDirFlag, SystemImageDirFlagImpl>()
+ .addMultibinding<FlagFeature, SystemImageDirFlag>();
+}
+
+fruit::Component<ConfigFlag> ConfigFlagPlaceholder() {
+ return fruit::createComponent()
+ .addMultibinding<FlagFeature, ConfigFlag>()
+ .bind<ConfigFlag, ConfigFlagPlaceholderImpl>();
+}
+
+} // namespace cuttlefish
diff --git a/host/libs/config/config_flag.h b/host/libs/config/config_flag.h
new file mode 100644
index 0000000..04258eb
--- /dev/null
+++ b/host/libs/config/config_flag.h
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#pragma once
+
+#include <fruit/fruit.h>
+#include <string>
+
+#include "host/libs/config/feature.h"
+
+namespace cuttlefish {
+
+// TODO(schuffelen): Move this to a more central location?
+class SystemImageDirFlag : public FlagFeature {
+ public:
+ virtual const std::string& Path() = 0;
+};
+
+class ConfigFlag : public FlagFeature {};
+
+fruit::Component<SystemImageDirFlag, ConfigFlag> ConfigFlagComponent();
+
+fruit::Component<ConfigFlag> ConfigFlagPlaceholder();
+
+} // namespace cuttlefish
diff --git a/common/libs/utils/size_utils.cpp b/host/libs/config/config_fragment.h
similarity index 64%
copy from common/libs/utils/size_utils.cpp
copy to host/libs/config/config_fragment.h
index 9f25445..cc1bfc1 100644
--- a/common/libs/utils/size_utils.cpp
+++ b/host/libs/config/config_fragment.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2018 The Android Open Source Project
+ * Copyright (C) 2021 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -13,16 +13,21 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
+#pragma once
-#include "common/libs/utils/size_utils.h"
-
-#include <unistd.h>
+#include <json/json.h>
+#include <memory>
+#include <string>
namespace cuttlefish {
-uint64_t AlignToPowerOf2(uint64_t val, uint8_t align_log) {
- uint64_t align = 1ULL << align_log;
- return ((val + (align - 1)) / align) * align;
-}
+class ConfigFragment {
+ public:
+ virtual ~ConfigFragment();
+
+ virtual std::string Name() const = 0;
+ virtual Json::Value Serialize() const = 0;
+ virtual bool Deserialize(const Json::Value&) = 0;
+};
} // namespace cuttlefish
diff --git a/host/libs/config/custom_actions.cpp b/host/libs/config/custom_actions.cpp
index 1cc2e06..af5524e 100644
--- a/host/libs/config/custom_actions.cpp
+++ b/host/libs/config/custom_actions.cpp
@@ -16,12 +16,16 @@
#include "host/libs/config/custom_actions.h"
#include <android-base/logging.h>
+#include <android-base/strings.h>
#include <json/json.h>
+#include <fstream>
#include <optional>
#include <string>
#include <vector>
+#include "common/libs/utils/files.h"
+#include "common/libs/utils/flag_parser.h"
#include "host/libs/config/cuttlefish_config.h"
namespace cuttlefish {
@@ -38,42 +42,41 @@
const char* kCustomActionButtonTitle = "title";
const char* kCustomActionButtonIconName = "icon_name";
-} //namespace
-
-
-CustomActionConfig::CustomActionConfig(const Json::Value& dictionary) {
- if (dictionary.isMember(kCustomActionShellCommand) +
- dictionary.isMember(kCustomActionServer) +
- dictionary.isMember(kCustomActionDeviceStates) !=
- 1) {
- LOG(FATAL) << "Custom action must contain exactly one of shell_command, "
+std::optional<CustomActionConfig> CustomActionConfigFromJson(
+ const Json::Value& dictionary) {
+ bool has_shell_command = dictionary.isMember(kCustomActionShellCommand);
+ bool has_server = dictionary.isMember(kCustomActionServer);
+ bool has_device_states = dictionary.isMember(kCustomActionDeviceStates);
+ if (!!has_shell_command + !!has_server + !!has_device_states != 1) {
+ LOG(ERROR) << "Custom action must contain exactly one of shell_command, "
<< "server, or device_states";
- return;
+ return {};
}
- if (dictionary.isMember(kCustomActionShellCommand)) {
+ CustomActionConfig config;
+ if (has_shell_command) {
// Shell command with one button.
Json::Value button_entry = dictionary[kCustomActionButton];
- buttons = {{button_entry[kCustomActionButtonCommand].asString(),
- button_entry[kCustomActionButtonTitle].asString(),
- button_entry[kCustomActionButtonIconName].asString()}};
- shell_command = dictionary[kCustomActionShellCommand].asString();
- } else if (dictionary.isMember(kCustomActionServer)) {
+ config.buttons = {{button_entry[kCustomActionButtonCommand].asString(),
+ button_entry[kCustomActionButtonTitle].asString(),
+ button_entry[kCustomActionButtonIconName].asString()}};
+ config.shell_command = dictionary[kCustomActionShellCommand].asString();
+ } else if (has_server) {
// Action server with possibly multiple buttons.
for (const Json::Value& button_entry : dictionary[kCustomActionButtons]) {
ControlPanelButton button = {
button_entry[kCustomActionButtonCommand].asString(),
button_entry[kCustomActionButtonTitle].asString(),
button_entry[kCustomActionButtonIconName].asString()};
- buttons.push_back(button);
+ config.buttons.push_back(button);
}
- server = dictionary[kCustomActionServer].asString();
- } else if (dictionary.isMember(kCustomActionDeviceStates)) {
+ config.server = dictionary[kCustomActionServer].asString();
+ } else if (has_device_states) {
// Device state(s) with one button.
// Each button press cycles to the next state, then repeats to the first.
Json::Value button_entry = dictionary[kCustomActionButton];
- buttons = {{button_entry[kCustomActionButtonCommand].asString(),
- button_entry[kCustomActionButtonTitle].asString(),
- button_entry[kCustomActionButtonIconName].asString()}};
+ config.buttons = {{button_entry[kCustomActionButtonCommand].asString(),
+ button_entry[kCustomActionButtonTitle].asString(),
+ button_entry[kCustomActionButtonIconName].asString()}};
for (const Json::Value& device_state_entry :
dictionary[kCustomActionDeviceStates]) {
DeviceState state;
@@ -86,40 +89,42 @@
state.hinge_angle_value =
device_state_entry[kCustomActionDeviceStateHingeAngleValue].asInt();
}
- device_states.push_back(state);
+ config.device_states.push_back(state);
}
} else {
- LOG(FATAL) << "Unknown custom action type.";
+ LOG(ERROR) << "Unknown custom action type.";
+ return {};
}
+ return config;
}
-Json::Value CustomActionConfig::ToJson() const {
- Json::Value custom_action;
- if (shell_command) {
+Json::Value ToJson(const CustomActionConfig& custom_action) {
+ Json::Value json;
+ if (custom_action.shell_command) {
// Shell command with one button.
- custom_action[kCustomActionShellCommand] = *shell_command;
- custom_action[kCustomActionButton] = Json::Value();
- custom_action[kCustomActionButton][kCustomActionButtonCommand] =
- buttons[0].command;
- custom_action[kCustomActionButton][kCustomActionButtonTitle] =
- buttons[0].title;
- custom_action[kCustomActionButton][kCustomActionButtonIconName] =
- buttons[0].icon_name;
- } else if (server) {
+ json[kCustomActionShellCommand] = *custom_action.shell_command;
+ json[kCustomActionButton] = Json::Value();
+ json[kCustomActionButton][kCustomActionButtonCommand] =
+ custom_action.buttons[0].command;
+ json[kCustomActionButton][kCustomActionButtonTitle] =
+ custom_action.buttons[0].title;
+ json[kCustomActionButton][kCustomActionButtonIconName] =
+ custom_action.buttons[0].icon_name;
+ } else if (custom_action.server) {
// Action server with possibly multiple buttons.
- custom_action[kCustomActionServer] = *server;
- custom_action[kCustomActionButtons] = Json::Value(Json::arrayValue);
- for (const auto& button : buttons) {
+ json[kCustomActionServer] = *custom_action.server;
+ json[kCustomActionButtons] = Json::Value(Json::arrayValue);
+ for (const auto& button : custom_action.buttons) {
Json::Value button_entry;
button_entry[kCustomActionButtonCommand] = button.command;
button_entry[kCustomActionButtonTitle] = button.title;
button_entry[kCustomActionButtonIconName] = button.icon_name;
- custom_action[kCustomActionButtons].append(button_entry);
+ json[kCustomActionButtons].append(button_entry);
}
- } else if (!device_states.empty()) {
+ } else if (!custom_action.device_states.empty()) {
// Device state(s) with one button.
- custom_action[kCustomActionDeviceStates] = Json::Value(Json::arrayValue);
- for (const auto& device_state : device_states) {
+ json[kCustomActionDeviceStates] = Json::Value(Json::arrayValue);
+ for (const auto& device_state : custom_action.device_states) {
Json::Value device_state_entry;
if (device_state.lid_switch_open) {
device_state_entry[kCustomActionDeviceStateLidSwitchOpen] =
@@ -129,19 +134,167 @@
device_state_entry[kCustomActionDeviceStateHingeAngleValue] =
*device_state.hinge_angle_value;
}
- custom_action[kCustomActionDeviceStates].append(device_state_entry);
+ json[kCustomActionDeviceStates].append(device_state_entry);
}
- custom_action[kCustomActionButton] = Json::Value();
- custom_action[kCustomActionButton][kCustomActionButtonCommand] =
- buttons[0].command;
- custom_action[kCustomActionButton][kCustomActionButtonTitle] =
- buttons[0].title;
- custom_action[kCustomActionButton][kCustomActionButtonIconName] =
- buttons[0].icon_name;
+ json[kCustomActionButton] = Json::Value();
+ json[kCustomActionButton][kCustomActionButtonCommand] =
+ custom_action.buttons[0].command;
+ json[kCustomActionButton][kCustomActionButtonTitle] =
+ custom_action.buttons[0].title;
+ json[kCustomActionButton][kCustomActionButtonIconName] =
+ custom_action.buttons[0].icon_name;
} else {
LOG(FATAL) << "Unknown custom action type.";
}
- return custom_action;
+ return json;
+}
+
+std::string DefaultCustomActionConfig() {
+ auto custom_action_config_dir =
+ DefaultHostArtifactsPath("etc/cvd_custom_action_config");
+ if (DirectoryExists(custom_action_config_dir)) {
+ auto custom_action_configs = DirectoryContents(custom_action_config_dir);
+ // Two entries are always . and ..
+ if (custom_action_configs.size() > 3) {
+ LOG(ERROR) << "Expected at most one custom action config in "
+ << custom_action_config_dir << ". Please delete extras.";
+ } else if (custom_action_configs.size() == 3) {
+ for (const auto& config : custom_action_configs) {
+ if (android::base::EndsWithIgnoreCase(config, ".json")) {
+ return custom_action_config_dir + "/" + config;
+ }
+ }
+ }
+ }
+ return "";
+}
+
+class CustomActionConfigImpl : public CustomActionConfigProvider {
+ public:
+ INJECT(CustomActionConfigImpl(ConfigFlag& config)) : config_(config) {
+ custom_action_config_flag_ = GflagsCompatFlag("custom_action_config");
+ custom_action_config_flag_.Help(
+ "Path to a custom action config JSON. Defaults to the file provided by "
+ "build variable CVD_CUSTOM_ACTION_CONFIG. If this build variable is "
+ "empty then the custom action config will be empty as well.");
+ custom_action_config_flag_.Getter(
+ [this]() { return custom_action_config_; });
+ custom_action_config_flag_.Setter([this](const FlagMatch& match) {
+ if (!match.value.empty() && !FileExists(match.value)) {
+ LOG(ERROR) << "custom_action_config file \"" << match.value << "\" "
+ << "does not exist.";
+ return false;
+ }
+ custom_action_config_ = match.value;
+ return true;
+ });
+ // TODO(schuffelen): Access ConfigFlag directly for these values.
+ custom_actions_flag_ = GflagsCompatFlag("custom_actions");
+ custom_actions_flag_.Help(
+ "Serialized JSON of an array of custom action objects (in the same "
+ "format as custom action config JSON files). For use within --config "
+ "preset config files; prefer --custom_action_config to specify a "
+ "custom config file on the command line. Actions in this flag are "
+ "combined with actions in --custom_action_config.");
+ custom_actions_flag_.Setter([this](const FlagMatch& match) {
+ // Load the custom action from the --config preset file.
+ Json::CharReaderBuilder builder;
+ std::unique_ptr<Json::CharReader> reader(builder.newCharReader());
+ std::string errorMessage;
+ Json::Value custom_action_array(Json::arrayValue);
+ if (!reader->parse(&*match.value.begin(), &*match.value.end(),
+ &custom_action_array, &errorMessage)) {
+ LOG(ERROR) << "Could not read custom actions config flag: "
+ << errorMessage;
+ return false;
+ }
+ return AddJsonCustomActionConfigs(custom_action_array);
+ });
+ }
+
+ const std::vector<CustomActionConfig>& CustomActions() const override {
+ return custom_actions_;
+ }
+
+ // ConfigFragment
+ Json::Value Serialize() const override {
+ Json::Value actions_array(Json::arrayValue);
+ for (const auto& action : CustomActions()) {
+ actions_array.append(ToJson(action));
+ }
+ return actions_array;
+ }
+ bool Deserialize(const Json::Value& custom_actions_json) override {
+ return AddJsonCustomActionConfigs(custom_actions_json);
+ }
+
+ // FlagFeature
+ std::string Name() const override { return "CustomActionConfig"; }
+ std::unordered_set<FlagFeature*> Dependencies() const override {
+ return {static_cast<FlagFeature*>(&config_)};
+ }
+
+ bool Process(std::vector<std::string>& args) override {
+ custom_action_config_ = DefaultCustomActionConfig();
+ if (!ParseFlags(Flags(), args)) {
+ return false;
+ }
+ if (custom_action_config_ != "") {
+ Json::CharReaderBuilder builder;
+ std::ifstream ifs(custom_action_config_);
+ std::string errorMessage;
+ Json::Value custom_action_array(Json::arrayValue);
+ if (!Json::parseFromStream(builder, ifs, &custom_action_array,
+ &errorMessage)) {
+ LOG(ERROR) << "Could not read custom actions config file "
+ << custom_action_config_ << ": " << errorMessage;
+ return false;
+ }
+ return AddJsonCustomActionConfigs(custom_action_array);
+ }
+ return true;
+ }
+ bool WriteGflagsCompatHelpXml(std::ostream& out) const override {
+ return WriteGflagsCompatXml(Flags(), out);
+ }
+
+ private:
+ std::vector<Flag> Flags() const {
+ return {custom_action_config_flag_, custom_actions_flag_};
+ }
+
+ bool AddJsonCustomActionConfigs(const Json::Value& custom_action_array) {
+ if (custom_action_array.type() != Json::arrayValue) {
+ LOG(ERROR) << "Expected a JSON array of custom actions";
+ return false;
+ }
+ for (const auto& custom_action_json : custom_action_array) {
+ auto custom_action = CustomActionConfigFromJson(custom_action_json);
+ if (custom_action) {
+ custom_actions_.push_back(*custom_action);
+ } else {
+ LOG(ERROR) << "Validation failed on a custom action";
+ return false;
+ }
+ }
+ return true;
+ }
+
+ ConfigFlag& config_;
+ Flag custom_action_config_flag_;
+ std::string custom_action_config_;
+ Flag custom_actions_flag_;
+ std::vector<CustomActionConfig> custom_actions_;
+};
+
+} // namespace
+
+fruit::Component<fruit::Required<ConfigFlag>, CustomActionConfigProvider>
+CustomActionsComponent() {
+ return fruit::createComponent()
+ .bind<CustomActionConfigProvider, CustomActionConfigImpl>()
+ .addMultibinding<ConfigFragment, CustomActionConfigProvider>()
+ .addMultibinding<FlagFeature, CustomActionConfigProvider>();
}
} // namespace cuttlefish
diff --git a/host/libs/config/custom_actions.h b/host/libs/config/custom_actions.h
index 6279401..73f3901 100644
--- a/host/libs/config/custom_actions.h
+++ b/host/libs/config/custom_actions.h
@@ -15,12 +15,15 @@
*/
#pragma once
-#include <json/json.h>
-
+#include <fruit/fruit.h>
#include <optional>
#include <string>
#include <vector>
+#include "host/libs/config/config_flag.h"
+#include "host/libs/config/config_fragment.h"
+#include "host/libs/config/feature.h"
+
namespace cuttlefish {
struct ControlPanelButton {
@@ -35,13 +38,18 @@
};
struct CustomActionConfig {
- CustomActionConfig(const Json::Value&);
- Json::Value ToJson() const;
-
std::vector<ControlPanelButton> buttons;
std::optional<std::string> shell_command;
std::optional<std::string> server;
std::vector<DeviceState> device_states;
};
+class CustomActionConfigProvider : public FlagFeature, public ConfigFragment {
+ public:
+ virtual const std::vector<CustomActionConfig>& CustomActions() const = 0;
+};
+
+fruit::Component<fruit::Required<ConfigFlag>, CustomActionConfigProvider>
+CustomActionsComponent();
+
} // namespace cuttlefish
diff --git a/host/libs/config/cuttlefish_config.cpp b/host/libs/config/cuttlefish_config.cpp
index b5160d0..4b169af 100644
--- a/host/libs/config/cuttlefish_config.cpp
+++ b/host/libs/config/cuttlefish_config.cpp
@@ -35,14 +35,32 @@
#include "common/libs/utils/environment.h"
#include "common/libs/utils/files.h"
#include "host/libs/vm_manager/crosvm_manager.h"
+#include "host/libs/vm_manager/gem5_manager.h"
#include "host/libs/vm_manager/qemu_manager.h"
namespace cuttlefish {
namespace {
+static constexpr int kDefaultInstance = 1;
+
+int InstanceFromString(std::string instance_str) {
+ if (android::base::StartsWith(instance_str, kVsocUserPrefix)) {
+ instance_str = instance_str.substr(std::string(kVsocUserPrefix).size());
+ } else if (android::base::StartsWith(instance_str, kCvdNamePrefix)) {
+ instance_str = instance_str.substr(std::string(kCvdNamePrefix).size());
+ }
+
+ int instance = std::stoi(instance_str);
+ if (instance <= 0) {
+ LOG(INFO) << "Failed to interpret \"" << instance_str << "\" as an id, "
+ << "using instance id " << kDefaultInstance;
+ return kDefaultInstance;
+ }
+ return instance;
+}
+
int InstanceFromEnvironment() {
static constexpr char kInstanceEnvironmentVariable[] = "CUTTLEFISH_INSTANCE";
- static constexpr int kDefaultInstance = 1;
// CUTTLEFISH_INSTANCE environment variable
std::string instance_str = StringFromEnv(kInstanceEnvironmentVariable, "");
@@ -60,15 +78,8 @@
LOG(DEBUG) << "Non-vsoc user, using instance id " << kDefaultInstance;
return kDefaultInstance;
}
- instance_str = instance_str.substr(std::string(kVsocUserPrefix).size());
}
- int instance = std::stoi(instance_str);
- if (instance <= 0) {
- LOG(INFO) << "Failed to interpret \"" << instance_str << "\" as an id, "
- << "using instance id " << kDefaultInstance;
- return kDefaultInstance;
- }
- return instance;
+ return InstanceFromString(instance_str);
}
const char* kInstances = "instances";
@@ -81,18 +92,47 @@
const char* const kGpuModeDrmVirgl = "drm_virgl";
const char* const kGpuModeGfxStream = "gfxstream";
+const char* const kHwComposerAuto = "auto";
+const char* const kHwComposerDrm = "drm";
+const char* const kHwComposerRanchu = "ranchu";
+
std::string DefaultEnvironmentPath(const char* environment_key,
const char* default_value,
const char* subpath) {
return StringFromEnv(environment_key, default_value) + "/" + subpath;
}
-static constexpr char kAssemblyDir[] = "assembly_dir";
-std::string CuttlefishConfig::assembly_dir() const {
- return (*dictionary_)[kAssemblyDir].asString();
+ConfigFragment::~ConfigFragment() = default;
+
+static constexpr char kFragments[] = "fragments";
+bool CuttlefishConfig::LoadFragment(ConfigFragment& fragment) const {
+ if (!dictionary_->isMember(kFragments)) {
+ LOG(ERROR) << "Fragments member was missing";
+ return false;
+ }
+ const Json::Value& json_fragments = (*dictionary_)[kFragments];
+ if (!json_fragments.isMember(fragment.Name())) {
+ LOG(ERROR) << "Could not find a fragment called " << fragment.Name();
+ return false;
+ }
+ return fragment.Deserialize(json_fragments[fragment.Name()]);
}
-void CuttlefishConfig::set_assembly_dir(const std::string& assembly_dir) {
- (*dictionary_)[kAssemblyDir] = assembly_dir;
+bool CuttlefishConfig::SaveFragment(const ConfigFragment& fragment) {
+ Json::Value& json_fragments = (*dictionary_)[kFragments];
+ if (json_fragments.isMember(fragment.Name())) {
+ LOG(ERROR) << "Already have a fragment called " << fragment.Name();
+ return false;
+ }
+ json_fragments[fragment.Name()] = fragment.Serialize();
+ return true;
+}
+
+static constexpr char kRootDir[] = "root_dir";
+std::string CuttlefishConfig::root_dir() const {
+ return (*dictionary_)[kRootDir].asString();
+}
+void CuttlefishConfig::set_root_dir(const std::string& root_dir) {
+ (*dictionary_)[kRootDir] = root_dir;
}
static constexpr char kVmManager[] = "vm_manager";
@@ -111,6 +151,38 @@
(*dictionary_)[kGpuMode] = name;
}
+static constexpr char kGpuCaptureBinary[] = "gpu_capture_binary";
+std::string CuttlefishConfig::gpu_capture_binary() const {
+ return (*dictionary_)[kGpuCaptureBinary].asString();
+}
+void CuttlefishConfig::set_gpu_capture_binary(const std::string& name) {
+ (*dictionary_)[kGpuCaptureBinary] = name;
+}
+
+static constexpr char kHWComposer[] = "hwcomposer";
+std::string CuttlefishConfig::hwcomposer() const {
+ return (*dictionary_)[kHWComposer].asString();
+}
+void CuttlefishConfig::set_hwcomposer(const std::string& name) {
+ (*dictionary_)[kHWComposer] = name;
+}
+
+static constexpr char kEnableGpuUdmabuf[] = "enable_gpu_udmabuf";
+void CuttlefishConfig::set_enable_gpu_udmabuf(const bool enable_gpu_udmabuf) {
+ (*dictionary_)[kEnableGpuUdmabuf] = enable_gpu_udmabuf;
+}
+bool CuttlefishConfig::enable_gpu_udmabuf() const {
+ return (*dictionary_)[kEnableGpuUdmabuf].asBool();
+}
+
+static constexpr char kEnableGpuAngle[] = "enable_gpu_angle";
+void CuttlefishConfig::set_enable_gpu_angle(const bool enable_gpu_angle) {
+ (*dictionary_)[kEnableGpuAngle] = enable_gpu_angle;
+}
+bool CuttlefishConfig::enable_gpu_angle() const {
+ return (*dictionary_)[kEnableGpuAngle].asBool();
+}
+
static constexpr char kCpus[] = "cpus";
int CuttlefishConfig::cpus() const { return (*dictionary_)[kCpus].asInt(); }
void CuttlefishConfig::set_cpus(int cpus) { (*dictionary_)[kCpus] = cpus; }
@@ -190,35 +262,6 @@
return (*dictionary_)[kCuttlefishEnvPath].asString();
}
-static AdbMode stringToAdbMode(std::string mode) {
- std::transform(mode.begin(), mode.end(), mode.begin(), ::tolower);
- if (mode == "vsock_tunnel") {
- return AdbMode::VsockTunnel;
- } else if (mode == "vsock_half_tunnel") {
- return AdbMode::VsockHalfTunnel;
- } else if (mode == "native_vsock") {
- return AdbMode::NativeVsock;
- } else {
- return AdbMode::Unknown;
- }
-}
-
-static constexpr char kAdbMode[] = "adb_mode";
-std::set<AdbMode> CuttlefishConfig::adb_mode() const {
- std::set<AdbMode> args_set;
- for (auto& mode : (*dictionary_)[kAdbMode]) {
- args_set.insert(stringToAdbMode(mode.asString()));
- }
- return args_set;
-}
-void CuttlefishConfig::set_adb_mode(const std::set<std::string>& mode) {
- Json::Value mode_json_obj(Json::arrayValue);
- for (const auto& arg : mode) {
- mode_json_obj.append(arg);
- }
- (*dictionary_)[kAdbMode] = mode_json_obj;
-}
-
static SecureHal StringToSecureHal(std::string mode) {
std::transform(mode.begin(), mode.end(), mode.begin(), ::tolower);
if (mode == "keymint") {
@@ -270,12 +313,12 @@
(*dictionary_)[kCrosvmBinary] = crosvm_binary;
}
-static constexpr char kTpmDevice[] = "tpm_device";
-std::string CuttlefishConfig::tpm_device() const {
- return (*dictionary_)[kTpmDevice].asString();
+static constexpr char kGem5BinaryDir[] = "gem5_binary_dir";
+std::string CuttlefishConfig::gem5_binary_dir() const {
+ return (*dictionary_)[kGem5BinaryDir].asString();
}
-void CuttlefishConfig::set_tpm_device(const std::string& tpm_device) {
- (*dictionary_)[kTpmDevice] = tpm_device;
+void CuttlefishConfig::set_gem5_binary_dir(const std::string& gem5_binary_dir) {
+ (*dictionary_)[kGem5BinaryDir] = gem5_binary_dir;
}
static constexpr char kEnableGnssGrpcProxy[] = "enable_gnss_grpc_proxy";
@@ -286,14 +329,6 @@
return (*dictionary_)[kEnableGnssGrpcProxy].asBool();
}
-static constexpr char kEnableVncServer[] = "enable_vnc_server";
-bool CuttlefishConfig::enable_vnc_server() const {
- return (*dictionary_)[kEnableVncServer].asBool();
-}
-void CuttlefishConfig::set_enable_vnc_server(bool enable_vnc_server) {
- (*dictionary_)[kEnableVncServer] = enable_vnc_server;
-}
-
static constexpr char kEnableSandbox[] = "enable_sandbox";
void CuttlefishConfig::set_enable_sandbox(const bool enable_sandbox) {
(*dictionary_)[kEnableSandbox] = enable_sandbox;
@@ -330,30 +365,6 @@
return (*dictionary_)[kEnableVehicleHalServer].asBool();
}
-static constexpr char kVehicleHalServerBinary[] = "vehicle_hal_server_binary";
-void CuttlefishConfig::set_vehicle_hal_grpc_server_binary(const std::string& vehicle_hal_server_binary) {
- (*dictionary_)[kVehicleHalServerBinary] = vehicle_hal_server_binary;
-}
-std::string CuttlefishConfig::vehicle_hal_grpc_server_binary() const {
- return (*dictionary_)[kVehicleHalServerBinary].asString();
-}
-
-static constexpr char kCustomActions[] = "custom_actions";
-void CuttlefishConfig::set_custom_actions(const std::vector<CustomActionConfig>& actions) {
- Json::Value actions_array(Json::arrayValue);
- for (const auto& action : actions) {
- actions_array.append(action.ToJson());
- }
- (*dictionary_)[kCustomActions] = actions_array;
-}
-std::vector<CustomActionConfig> CuttlefishConfig::custom_actions() const {
- std::vector<CustomActionConfig> result;
- for (Json::Value custom_action : (*dictionary_)[kCustomActions]) {
- result.push_back(CustomActionConfig(custom_action));
- }
- return result;
-}
-
static constexpr char kWebRTCAssetsDir[] = "webrtc_assets_dir";
void CuttlefishConfig::set_webrtc_assets_dir(const std::string& webrtc_assets_dir) {
(*dictionary_)[kWebRTCAssetsDir] = webrtc_assets_dir;
@@ -379,14 +390,6 @@
(*dictionary_)[kRestartSubprocesses] = restart_subprocesses;
}
-static constexpr char kRunAdbConnector[] = "run_adb_connector";
-bool CuttlefishConfig::run_adb_connector() const {
- return (*dictionary_)[kRunAdbConnector].asBool();
-}
-void CuttlefishConfig::set_run_adb_connector(bool run_adb_connector) {
- (*dictionary_)[kRunAdbConnector] = run_adb_connector;
-}
-
static constexpr char kRunAsDaemon[] = "run_as_daemon";
bool CuttlefishConfig::run_as_daemon() const {
return (*dictionary_)[kRunAsDaemon].asBool();
@@ -411,14 +414,6 @@
(*dictionary_)[kBlankDataImageMb] = blank_data_image_mb;
}
-static constexpr char kBlankDataImageFmt[] = "blank_data_image_fmt";
-std::string CuttlefishConfig::blank_data_image_fmt() const {
- return (*dictionary_)[kBlankDataImageFmt].asString();
-}
-void CuttlefishConfig::set_blank_data_image_fmt(const std::string& blank_data_image_fmt) {
- (*dictionary_)[kBlankDataImageFmt] = blank_data_image_fmt;
-}
-
static constexpr char kBootloader[] = "bootloader";
std::string CuttlefishConfig::bootloader() const {
return (*dictionary_)[kBootloader].asString();
@@ -498,6 +493,14 @@
return (*dictionary_)[kSigServerPath].asString();
}
+static constexpr char kSigServerSecure[] = "webrtc_sig_server_secure";
+void CuttlefishConfig::set_sig_server_secure(bool secure) {
+ (*dictionary_)[kSigServerSecure] = secure;
+}
+bool CuttlefishConfig::sig_server_secure() const {
+ return (*dictionary_)[kSigServerSecure].asBool();
+}
+
static constexpr char kSigServerStrict[] = "webrtc_sig_server_strict";
void CuttlefishConfig::set_sig_server_strict(bool strict) {
(*dictionary_)[kSigServerStrict] = strict;
@@ -570,14 +573,6 @@
return (*dictionary_)[kGuestEnforceSecurity].asBool();
}
-const char* kGuestAuditSecurity = "guest_audit_security";
-void CuttlefishConfig::set_guest_audit_security(bool guest_audit_security) {
- (*dictionary_)[kGuestAuditSecurity] = guest_audit_security;
-}
-bool CuttlefishConfig::guest_audit_security() const {
- return (*dictionary_)[kGuestAuditSecurity].asBool();
-}
-
static constexpr char kenableHostBluetooth[] = "enable_host_bluetooth";
void CuttlefishConfig::set_enable_host_bluetooth(bool enable_host_bluetooth) {
(*dictionary_)[kenableHostBluetooth] = enable_host_bluetooth;
@@ -615,7 +610,8 @@
}
static constexpr char kExtraKernelCmdline[] = "extra_kernel_cmdline";
-void CuttlefishConfig::set_extra_kernel_cmdline(std::string extra_cmdline) {
+void CuttlefishConfig::set_extra_kernel_cmdline(
+ const std::string& extra_cmdline) {
Json::Value args_json_obj(Json::arrayValue);
for (const auto& arg : android::base::Split(extra_cmdline, " ")) {
args_json_obj.append(arg);
@@ -630,6 +626,23 @@
return cmdline;
}
+static constexpr char kExtraBootconfigArgs[] = "extra_bootconfig_args";
+void CuttlefishConfig::set_extra_bootconfig_args(
+ const std::string& extra_bootconfig_args) {
+ Json::Value args_json_obj(Json::arrayValue);
+ for (const auto& arg : android::base::Split(extra_bootconfig_args, " ")) {
+ args_json_obj.append(arg);
+ }
+ (*dictionary_)[kExtraBootconfigArgs] = args_json_obj;
+}
+std::vector<std::string> CuttlefishConfig::extra_bootconfig_args() const {
+ std::vector<std::string> bootconfig;
+ for (const Json::Value& arg : (*dictionary_)[kExtraBootconfigArgs]) {
+ bootconfig.push_back(arg.asString());
+ }
+ return bootconfig;
+}
+
static constexpr char kRilDns[] = "ril_dns";
void CuttlefishConfig::set_ril_dns(const std::string& ril_dns) {
(*dictionary_)[kRilDns] = ril_dns;
@@ -664,7 +677,8 @@
std::string CuttlefishConfig::console_dev() const {
auto can_use_virtio_console = !kgdb() && !use_bootloader();
std::string console_dev;
- if (can_use_virtio_console) {
+ if (can_use_virtio_console ||
+ vm_manager() == vm_manager::Gem5Manager::name()) {
// If kgdb and the bootloader are disabled, the Android serial console
// spawns on a virtio-console port. If the bootloader is enabled, virtio
// console can't be used since uboot doesn't support it.
@@ -690,6 +704,91 @@
return (*dictionary_)[kVhostNet].asBool();
}
+static constexpr char kVhostUserMac80211Hwsim[] = "vhost_user_mac80211_hwsim";
+void CuttlefishConfig::set_vhost_user_mac80211_hwsim(const std::string& path) {
+ (*dictionary_)[kVhostUserMac80211Hwsim] = path;
+}
+std::string CuttlefishConfig::vhost_user_mac80211_hwsim() const {
+ return (*dictionary_)[kVhostUserMac80211Hwsim].asString();
+}
+
+static constexpr char kWmediumdApiServerSocket[] = "wmediumd_api_server_socket";
+void CuttlefishConfig::set_wmediumd_api_server_socket(const std::string& path) {
+ (*dictionary_)[kWmediumdApiServerSocket] = path;
+}
+std::string CuttlefishConfig::wmediumd_api_server_socket() const {
+ return (*dictionary_)[kWmediumdApiServerSocket].asString();
+}
+
+static constexpr char kApRootfsImage[] = "ap_rootfs_image";
+std::string CuttlefishConfig::ap_rootfs_image() const {
+ return (*dictionary_)[kApRootfsImage].asString();
+}
+void CuttlefishConfig::set_ap_rootfs_image(const std::string& ap_rootfs_image) {
+ (*dictionary_)[kApRootfsImage] = ap_rootfs_image;
+}
+
+static constexpr char kApKernelImage[] = "ap_kernel_image";
+std::string CuttlefishConfig::ap_kernel_image() const {
+ return (*dictionary_)[kApKernelImage].asString();
+}
+void CuttlefishConfig::set_ap_kernel_image(const std::string& ap_kernel_image) {
+ (*dictionary_)[kApKernelImage] = ap_kernel_image;
+}
+
+static constexpr char kWmediumdConfig[] = "wmediumd_config";
+void CuttlefishConfig::set_wmediumd_config(const std::string& config) {
+ (*dictionary_)[kWmediumdConfig] = config;
+}
+std::string CuttlefishConfig::wmediumd_config() const {
+ return (*dictionary_)[kWmediumdConfig].asString();
+}
+
+static constexpr char kRootcanalHciPort[] = "rootcanal_hci_port";
+int CuttlefishConfig::rootcanal_hci_port() const {
+ return (*dictionary_)[kRootcanalHciPort].asInt();
+}
+void CuttlefishConfig::set_rootcanal_hci_port(int rootcanal_hci_port) {
+ (*dictionary_)[kRootcanalHciPort] = rootcanal_hci_port;
+}
+
+static constexpr char kRootcanalLinkPort[] = "rootcanal_link_port";
+int CuttlefishConfig::rootcanal_link_port() const {
+ return (*dictionary_)[kRootcanalLinkPort].asInt();
+}
+void CuttlefishConfig::set_rootcanal_link_port(int rootcanal_link_port) {
+ (*dictionary_)[kRootcanalLinkPort] = rootcanal_link_port;
+}
+
+static constexpr char kRootcanalTestPort[] = "rootcanal_test_port";
+int CuttlefishConfig::rootcanal_test_port() const {
+ return (*dictionary_)[kRootcanalTestPort].asInt();
+}
+void CuttlefishConfig::set_rootcanal_test_port(int rootcanal_test_port) {
+ (*dictionary_)[kRootcanalTestPort] = rootcanal_test_port;
+}
+
+static constexpr char kRootcanalConfigFile[] = "rootcanal_config_file";
+std::string CuttlefishConfig::rootcanal_config_file() const {
+ return (*dictionary_)[kRootcanalConfigFile].asString();
+}
+void CuttlefishConfig::set_rootcanal_config_file(
+ const std::string& rootcanal_config_file) {
+ (*dictionary_)[kRootcanalConfigFile] =
+ DefaultHostArtifactsPath(rootcanal_config_file);
+}
+
+static constexpr char kRootcanalDefaultCommandsFile[] =
+ "rootcanal_default_commands_file";
+std::string CuttlefishConfig::rootcanal_default_commands_file() const {
+ return (*dictionary_)[kRootcanalDefaultCommandsFile].asString();
+}
+void CuttlefishConfig::set_rootcanal_default_commands_file(
+ const std::string& rootcanal_default_commands_file) {
+ (*dictionary_)[kRootcanalDefaultCommandsFile] =
+ DefaultHostArtifactsPath(rootcanal_default_commands_file);
+}
+
static constexpr char kRecordScreen[] = "record_screen";
void CuttlefishConfig::set_record_screen(bool record_screen) {
(*dictionary_)[kRecordScreen] = record_screen;
@@ -738,15 +837,29 @@
(*dictionary_)[kBootconfigSupported] = bootconfig_supported;
}
-// Creates the (initially empty) config object and populates it with values from
-// the config file if the CUTTLEFISH_CONFIG_FILE env variable is present.
-// Returns nullptr if there was an error loading from file
-/*static*/ CuttlefishConfig* CuttlefishConfig::BuildConfigImpl() {
- auto config_file_path = StringFromEnv(kCuttlefishConfigEnvVarName,
- GetGlobalConfigFileLink());
+static constexpr char kUserdataFormat[] = "userdata_format";
+std::string CuttlefishConfig::userdata_format() const {
+ return (*dictionary_)[kUserdataFormat].asString();
+}
+void CuttlefishConfig::set_userdata_format(const std::string& userdata_format) {
+ auto fmt = userdata_format;
+ std::transform(fmt.begin(), fmt.end(), fmt.begin(), ::tolower);
+ (*dictionary_)[kUserdataFormat] = fmt;
+}
+
+static constexpr char kApImageDevPath[] = "ap_image_dev_path";
+std::string CuttlefishConfig::ap_image_dev_path() const {
+ return (*dictionary_)[kApImageDevPath].asString();
+}
+void CuttlefishConfig::set_ap_image_dev_path(const std::string& dev_path) {
+ (*dictionary_)[kApImageDevPath] = dev_path;
+}
+
+/*static*/ CuttlefishConfig* CuttlefishConfig::BuildConfigImpl(
+ const std::string& path) {
auto ret = new CuttlefishConfig();
if (ret) {
- auto loaded = ret->LoadFromFile(config_file_path.c_str());
+ auto loaded = ret->LoadFromFile(path.c_str());
if (!loaded) {
delete ret;
return nullptr;
@@ -755,8 +868,19 @@
return ret;
}
+/*static*/ std::unique_ptr<const CuttlefishConfig>
+CuttlefishConfig::GetFromFile(const std::string& path) {
+ return std::unique_ptr<const CuttlefishConfig>(BuildConfigImpl(path));
+}
+
+// Creates the (initially empty) config object and populates it with values from
+// the config file if the CUTTLEFISH_CONFIG_FILE env variable is present.
+// Returns nullptr if there was an error loading from file
/*static*/ const CuttlefishConfig* CuttlefishConfig::Get() {
- static std::shared_ptr<CuttlefishConfig> config(BuildConfigImpl());
+ auto config_file_path =
+ StringFromEnv(kCuttlefishConfigEnvVarName, GetGlobalConfigFileLink());
+ static std::shared_ptr<CuttlefishConfig> config(
+ BuildConfigImpl(config_file_path));
return config.get();
}
@@ -800,11 +924,28 @@
return !ofs.fail();
}
+std::string CuttlefishConfig::instances_dir() const {
+ return AbsolutePath(root_dir() + "/instances");
+}
+
+std::string CuttlefishConfig::InstancesPath(
+ const std::string& file_name) const {
+ return AbsolutePath(instances_dir() + "/" + file_name);
+}
+
+std::string CuttlefishConfig::assembly_dir() const {
+ return AbsolutePath(root_dir() + "/assembly");
+}
+
std::string CuttlefishConfig::AssemblyPath(
const std::string& file_name) const {
return AbsolutePath(assembly_dir() + "/" + file_name);
}
+std::string CuttlefishConfig::os_composite_disk_path() const {
+ return AssemblyPath("os_composite.img");
+}
+
CuttlefishConfig::MutableInstanceSpecific CuttlefishConfig::ForInstance(int num) {
return MutableInstanceSpecific(this, std::to_string(num));
}
@@ -813,8 +954,13 @@
return InstanceSpecific(this, std::to_string(num));
}
+CuttlefishConfig::InstanceSpecific CuttlefishConfig::ForInstanceName(
+ const std::string& name) const {
+ return ForInstance(InstanceFromString(name));
+}
+
CuttlefishConfig::InstanceSpecific CuttlefishConfig::ForDefaultInstance() const {
- return InstanceSpecific(this, std::to_string(GetInstance()));
+ return ForInstance(GetInstance());
}
std::vector<CuttlefishConfig::InstanceSpecific> CuttlefishConfig::Instances() const {
@@ -826,6 +972,39 @@
return instances;
}
+std::vector<std::string> CuttlefishConfig::instance_dirs() const {
+ std::vector<std::string> result;
+ for (const auto& instance : Instances()) {
+ result.push_back(instance.instance_dir());
+ }
+ return result;
+}
+
+static constexpr char kInstanceNames[] = "instance_names";
+void CuttlefishConfig::set_instance_names(
+ const std::vector<std::string>& instance_names) {
+ Json::Value args_json_obj(Json::arrayValue);
+ for (const auto& name : instance_names) {
+ args_json_obj.append(name);
+ }
+ (*dictionary_)[kInstanceNames] = args_json_obj;
+}
+std::vector<std::string> CuttlefishConfig::instance_names() const {
+ // NOTE: The structure of this field needs to remain stable, since
+ // cvd_server may call this on config JSON files from various builds.
+ //
+ // This info is duplicated into its own field here so it is simpler
+ // to keep stable, rather than parsing from Instances()::instance_name.
+ //
+ // Any non-stable changes must be accompanied by an uprev to the
+ // cvd_server major version.
+ std::vector<std::string> names;
+ for (const Json::Value& name : (*dictionary_)[kInstanceNames]) {
+ names.push_back(name.asString());
+ }
+ return names;
+}
+
int GetInstance() {
static int instance_id = InstanceFromEnvironment();
return instance_id;
@@ -864,7 +1043,7 @@
}
std::string DefaultHostArtifactsPath(const std::string& file_name) {
- return (StringFromEnv("ANDROID_SOONG_HOST_OUT", StringFromEnv("HOME", ".")) + "/") +
+ return (StringFromEnv("ANDROID_HOST_OUT", StringFromEnv("HOME", ".")) + "/") +
file_name;
}
diff --git a/host/libs/config/cuttlefish_config.h b/host/libs/config/cuttlefish_config.h
index db3207e..78861e0 100644
--- a/host/libs/config/cuttlefish_config.h
+++ b/host/libs/config/cuttlefish_config.h
@@ -26,7 +26,7 @@
#include <vector>
#include "common/libs/utils/environment.h"
-#include "host/libs/config/custom_actions.h"
+#include "host/libs/config/config_fragment.h"
namespace Json {
class Value;
@@ -35,13 +35,11 @@
namespace cuttlefish {
constexpr char kLogcatSerialMode[] = "serial";
constexpr char kLogcatVsockMode[] = "vsock";
-}
-
-namespace cuttlefish {
constexpr char kDefaultUuidPrefix[] = "699acfc4-c8c4-11e7-882b-5065f31dc1";
constexpr char kCuttlefishConfigEnvVarName[] = "CUTTLEFISH_CONFIG_FILE";
constexpr char kVsocUserPrefix[] = "vsoc-";
+constexpr char kCvdNamePrefix[] = "cvd-";
constexpr char kBootStartedMessage[] ="VIRTUAL_DEVICE_BOOT_STARTED";
constexpr char kBootCompletedMessage[] = "VIRTUAL_DEVICE_BOOT_COMPLETED";
constexpr char kBootFailedMessage[] = "VIRTUAL_DEVICE_BOOT_FAILED";
@@ -56,14 +54,10 @@
"VIRTUAL_DEVICE_DISPLAY_POWER_MODE_CHANGED";
constexpr char kInternalDirName[] = "internal";
constexpr char kSharedDirName[] = "shared";
+constexpr char kLogDirName[] = "logs";
constexpr char kCrosvmVarEmptyDir[] = "/var/empty";
-
-enum class AdbMode {
- VsockTunnel,
- VsockHalfTunnel,
- NativeVsock,
- Unknown,
-};
+constexpr char kKernelLoadedMessage[] = "] Linux version";
+constexpr char kBootloaderLoadedMessage[] = "U-Boot 20";
enum class SecureHal {
Unknown,
@@ -75,6 +69,8 @@
class CuttlefishConfig {
public:
static const CuttlefishConfig* Get();
+ static std::unique_ptr<const CuttlefishConfig> GetFromFile(
+ const std::string& path);
static bool ConfigExists();
CuttlefishConfig();
@@ -86,17 +82,38 @@
// processes by passing the --config_file option.
bool SaveToFile(const std::string& file) const;
- std::string assembly_dir() const;
- void set_assembly_dir(const std::string& assembly_dir);
+ bool SaveFragment(const ConfigFragment&);
+ bool LoadFragment(ConfigFragment&) const;
+ std::string root_dir() const;
+ void set_root_dir(const std::string& root_dir);
+
+ std::string instances_dir() const;
+ std::string InstancesPath(const std::string&) const;
+
+ std::string assembly_dir() const;
std::string AssemblyPath(const std::string&) const;
+ std::string os_composite_disk_path() const;
+
std::string vm_manager() const;
void set_vm_manager(const std::string& name);
std::string gpu_mode() const;
void set_gpu_mode(const std::string& name);
+ std::string gpu_capture_binary() const;
+ void set_gpu_capture_binary(const std::string&);
+
+ std::string hwcomposer() const;
+ void set_hwcomposer(const std::string&);
+
+ void set_enable_gpu_udmabuf(const bool enable_gpu_udmabuf);
+ bool enable_gpu_udmabuf() const;
+
+ void set_enable_gpu_angle(const bool enable_gpu_angle);
+ bool enable_gpu_angle() const;
+
int cpus() const;
void set_cpus(int cpus);
@@ -122,9 +139,6 @@
void set_cuttlefish_env_path(const std::string& path);
std::string cuttlefish_env_path() const;
- void set_adb_mode(const std::set<std::string>& modes);
- std::set<AdbMode> adb_mode() const;
-
void set_secure_hals(const std::set<std::string>& hals);
std::set<SecureHal> secure_hals() const;
@@ -137,11 +151,8 @@
void set_crosvm_binary(const std::string& crosvm_binary);
std::string crosvm_binary() const;
- void set_tpm_device(const std::string& tpm_device);
- std::string tpm_device() const;
-
- void set_enable_vnc_server(bool enable_vnc_server);
- bool enable_vnc_server() const;
+ void set_gem5_binary_dir(const std::string& gem5_binary_dir);
+ std::string gem5_binary_dir() const;
void set_enable_sandbox(const bool enable_sandbox);
bool enable_sandbox() const;
@@ -161,18 +172,9 @@
void set_enable_vehicle_hal_grpc_server(bool enable_vhal_server);
bool enable_vehicle_hal_grpc_server() const;
- void set_vehicle_hal_grpc_server_binary(const std::string& vhal_server_binary);
- std::string vehicle_hal_grpc_server_binary() const;
-
- void set_custom_actions(const std::vector<CustomActionConfig>& actions);
- std::vector<CustomActionConfig> custom_actions() const;
-
void set_restart_subprocesses(bool restart_subprocesses);
bool restart_subprocesses() const;
- void set_run_adb_connector(bool run_adb_connector);
- bool run_adb_connector() const;
-
void set_enable_gnss_grpc_proxy(const bool enable_gnss_grpc_proxy);
bool enable_gnss_grpc_proxy() const;
@@ -185,9 +187,6 @@
void set_blank_data_image_mb(int blank_data_image_mb);
int blank_data_image_mb() const;
- void set_blank_data_image_fmt(const std::string& blank_data_image_fmt);
- std::string blank_data_image_fmt() const;
-
void set_bootloader(const std::string& bootloader_path);
std::string bootloader() const;
@@ -203,9 +202,6 @@
void set_guest_enforce_security(bool guest_enforce_security);
bool guest_enforce_security() const;
- void set_guest_audit_security(bool guest_audit_security);
- bool guest_audit_security() const;
-
void set_enable_host_bluetooth(bool enable_host_bluetooth);
bool enable_host_bluetooth() const;
@@ -221,9 +217,12 @@
void set_metrics_binary(const std::string& metrics_binary);
std::string metrics_binary() const;
- void set_extra_kernel_cmdline(std::string extra_cmdline);
+ void set_extra_kernel_cmdline(const std::string& extra_cmdline);
std::vector<std::string> extra_kernel_cmdline() const;
+ void set_extra_bootconfig_args(const std::string& extra_bootconfig_args);
+ std::vector<std::string> extra_bootconfig_args() const;
+
// A directory containing the SSL certificates for the signaling server
void set_webrtc_certs_dir(const std::string& certs_dir);
std::string webrtc_certs_dir() const;
@@ -250,6 +249,11 @@
void set_sig_server_path(const std::string& path);
std::string sig_server_path() const;
+ // Whether the webrtc process should use a secure connection (WSS) to the
+ // signaling server.
+ void set_sig_server_secure(bool secure);
+ bool sig_server_secure() const;
+
// Whether the webrtc process should attempt to verify the authenticity of the
// signaling server (reject self signed certificates)
void set_sig_server_strict(bool strict);
@@ -292,6 +296,37 @@
void set_vhost_net(bool vhost_net);
bool vhost_net() const;
+ void set_vhost_user_mac80211_hwsim(const std::string& path);
+ std::string vhost_user_mac80211_hwsim() const;
+
+ void set_wmediumd_api_server_socket(const std::string& path);
+ std::string wmediumd_api_server_socket() const;
+
+ void set_ap_rootfs_image(const std::string& path);
+ std::string ap_rootfs_image() const;
+
+ void set_ap_kernel_image(const std::string& path);
+ std::string ap_kernel_image() const;
+
+ void set_wmediumd_config(const std::string& path);
+ std::string wmediumd_config() const;
+
+ void set_rootcanal_hci_port(int rootcanal_hci_port);
+ int rootcanal_hci_port() const;
+
+ void set_rootcanal_link_port(int rootcanal_link_port);
+ int rootcanal_link_port() const;
+
+ void set_rootcanal_test_port(int rootcanal_test_port);
+ int rootcanal_test_port() const;
+
+ void set_rootcanal_config_file(const std::string& rootcanal_config_file);
+ std::string rootcanal_config_file() const;
+
+ void set_rootcanal_default_commands_file(
+ const std::string& rootcanal_default_commands_file);
+ std::string rootcanal_default_commands_file() const;
+
void set_record_screen(bool record_screen);
bool record_screen() const;
@@ -310,21 +345,32 @@
void set_bootconfig_supported(bool bootconfig_supported);
bool bootconfig_supported() const;
+ void set_userdata_format(const std::string& userdata_format);
+ std::string userdata_format() const;
+
+ // The path of an AP image in composite disk
+ std::string ap_image_dev_path() const;
+ void set_ap_image_dev_path(const std::string& dev_path);
+
class InstanceSpecific;
class MutableInstanceSpecific;
MutableInstanceSpecific ForInstance(int instance_num);
InstanceSpecific ForInstance(int instance_num) const;
+ InstanceSpecific ForInstanceName(const std::string& name) const;
InstanceSpecific ForDefaultInstance() const;
std::vector<InstanceSpecific> Instances() const;
+ std::vector<std::string> instance_dirs() const;
+
+ void set_instance_names(const std::vector<std::string>& instance_names);
+ std::vector<std::string> instance_names() const;
// A view into an existing CuttlefishConfig object for a particular instance.
class InstanceSpecific {
const CuttlefishConfig* config_;
std::string id_;
friend InstanceSpecific CuttlefishConfig::ForInstance(int num) const;
- friend InstanceSpecific CuttlefishConfig::ForDefaultInstance() const;
friend std::vector<InstanceSpecific> CuttlefishConfig::Instances() const;
InstanceSpecific(const CuttlefishConfig* config, const std::string& id)
@@ -337,8 +383,8 @@
// If any of the following port numbers is 0, the relevant service is not
// running on the guest.
- // Port number to connect to vnc server on the host
- int vnc_server_port() const;
+ // Port number for qemu to run a vnc server on the host
+ int qemu_vnc_server_port() const;
// Port number to connect to the tombstone receiver on the host
int tombstone_receiver_port() const;
// Port number to connect to the config server on the host
@@ -349,29 +395,21 @@
// Port number to connect to the touch server on the host. (Only
// operational if QEMU is the vmm.)
int touch_server_port() const;
- // Port number to connect to the frame server on the host. (Only
- // operational if using swiftshader as the GPU.)
- int frames_server_port() const;
// Port number to connect to the vehicle HAL server on the host
int vehicle_hal_server_port() const;
// Port number to connect to the audiocontrol server on the guest
int audiocontrol_server_port() const;
// Port number to connect to the adb server on the host
- int host_port() const;
+ int adb_host_port() const;
+ // Device-specific ID to distinguish modem simulators. Must be 4 digits.
+ int modem_simulator_host_id() const;
// Port number to connect to the gnss grpc proxy server on the host
int gnss_grpc_proxy_server_port() const;
std::string adb_ip_and_port() const;
- // Port number to connect to the root-canal on the host
- int rootcanal_hci_port() const;
- int rootcanal_link_port() const;
- int rootcanal_test_port() const;
// Port number to connect to the camera hal on the guest
int camera_server_port() const;
- std::string rootcanal_config_file() const;
- std::string rootcanal_default_commands_file() const;
std::string adb_device_name() const;
- std::string device_title() const;
std::string gnss_file_path() const;
std::string mobile_bridge_name() const;
std::string mobile_tap_name() const;
@@ -388,6 +426,7 @@
// directory..
std::string PerInstancePath(const char* file_name) const;
std::string PerInstanceInternalPath(const char* file_name) const;
+ std::string PerInstanceLogPath(const std::string& file_name) const;
std::string instance_dir() const;
@@ -398,11 +437,12 @@
std::string switches_socket_path() const;
std::string frames_socket_path() const;
- // mock hal guest socket that will be vsock/virtio later on
- std::string confui_hal_guest_socket_path() const;
+ int confui_host_vsock_port() const;
std::string access_kregistry_path() const;
+ std::string hwcomposer_pmem_path() const;
+
std::string pstore_path() const;
std::string console_path() const;
@@ -427,14 +467,10 @@
std::string sdcard_path() const;
- std::string os_composite_disk_path() const;
-
std::string persistent_composite_disk_path() const;
std::string uboot_env_image_path() const;
- std::string vendor_boot_image_path() const;
-
std::string audio_server_path() const;
// modem simulator related
@@ -447,12 +483,29 @@
// Whether this instance should start the webrtc signaling server
bool start_webrtc_sig_server() const;
+ // Whether to start a reverse proxy to the webrtc signaling server already
+ // running in the host
+ bool start_webrtc_sig_server_proxy() const;
+
+ // Whether this instance should start the wmediumd process
+ bool start_wmediumd() const;
+
+ // Whether this instance should start a rootcanal instance
+ bool start_rootcanal() const;
+
+ // Whether this instance should start an ap instance
+ bool start_ap() const;
+
// Wifi MAC address inside the guest
- std::array<unsigned char, 6> wifi_mac_address() const;
+ int wifi_mac_prefix() const;
std::string factory_reset_protected_path() const;
std::string persistent_bootconfig_path() const;
+
+ std::string vbmeta_path() const;
+
+ std::string id() const;
};
// A view into an existing CuttlefishConfig object for a particular instance.
@@ -461,13 +514,12 @@
std::string id_;
friend MutableInstanceSpecific CuttlefishConfig::ForInstance(int num);
- MutableInstanceSpecific(CuttlefishConfig* config, const std::string& id)
- : config_(config), id_(id) {}
+ MutableInstanceSpecific(CuttlefishConfig* config, const std::string& id);
Json::Value* Dictionary();
public:
void set_serial_number(const std::string& serial_number);
- void set_vnc_server_port(int vnc_server_port);
+ void set_qemu_vnc_server_port(int qemu_vnc_server_port);
void set_tombstone_receiver_port(int tombstone_receiver_port);
void set_config_server_port(int config_server_port);
void set_frames_server_port(int config_server_port);
@@ -477,16 +529,11 @@
void set_keymaster_vsock_port(int keymaster_vsock_port);
void set_vehicle_hal_server_port(int vehicle_server_port);
void set_audiocontrol_server_port(int audiocontrol_server_port);
- void set_host_port(int host_port);
+ void set_adb_host_port(int adb_host_port);
+ void set_modem_simulator_host_id(int modem_simulator_id);
void set_adb_ip_and_port(const std::string& ip_port);
- void set_rootcanal_hci_port(int rootcanal_hci_port);
- void set_rootcanal_link_port(int rootcanal_link_port);
- void set_rootcanal_test_port(int rootcanal_test_port);
+ void set_confui_host_vsock_port(int confui_host_port);
void set_camera_server_port(int camera_server_port);
- void set_rootcanal_config_file(const std::string& rootcanal_config_file);
- void set_rootcanal_default_commands_file(
- const std::string& rootcanal_default_commands_file);
- void set_device_title(const std::string& title);
void set_mobile_bridge_name(const std::string& mobile_bridge_name);
void set_mobile_tap_name(const std::string& mobile_tap_name);
void set_wifi_tap_name(const std::string& wifi_tap_name);
@@ -495,14 +542,17 @@
void set_use_allocd(bool use_allocd);
void set_vsock_guest_cid(int vsock_guest_cid);
void set_uuid(const std::string& uuid);
- void set_instance_dir(const std::string& instance_dir);
// modem simulator related
void set_modem_simulator_ports(const std::string& modem_simulator_ports);
void set_virtual_disk_paths(const std::vector<std::string>& disk_paths);
void set_webrtc_device_id(const std::string& id);
void set_start_webrtc_signaling_server(bool start);
+ void set_start_webrtc_sig_server_proxy(bool start);
+ void set_start_wmediumd(bool start);
+ void set_start_rootcanal(bool start);
+ void set_start_ap(bool start);
// Wifi MAC address inside the guest
- void set_wifi_mac_address(const std::array<unsigned char, 6>&);
+ void set_wifi_mac_prefix(const int wifi_mac_prefix);
// Gnss grpc proxy server port inside the host
void set_gnss_grpc_proxy_server_port(int gnss_grpc_proxy_server_port);
// Gnss grpc proxy local file path
@@ -514,7 +564,7 @@
void SetPath(const std::string& key, const std::string& path);
bool LoadFromFile(const char* file);
- static CuttlefishConfig* BuildConfigImpl();
+ static CuttlefishConfig* BuildConfigImpl(const std::string& path);
CuttlefishConfig(const CuttlefishConfig&) = delete;
CuttlefishConfig& operator=(const CuttlefishConfig&) = delete;
@@ -561,4 +611,9 @@
extern const char* const kGpuModeGuestSwiftshader;
extern const char* const kGpuModeDrmVirgl;
extern const char* const kGpuModeGfxStream;
+
+// HwComposer modes
+extern const char* const kHwComposerAuto;
+extern const char* const kHwComposerDrm;
+extern const char* const kHwComposerRanchu;
} // namespace cuttlefish
diff --git a/host/libs/config/cuttlefish_config_instance.cpp b/host/libs/config/cuttlefish_config_instance.cpp
index 14e7037..4f1dd33 100644
--- a/host/libs/config/cuttlefish_config_instance.cpp
+++ b/host/libs/config/cuttlefish_config_instance.cpp
@@ -26,8 +26,18 @@
const char* kInstances = "instances";
+std::string IdToName(const std::string& id) { return kCvdNamePrefix + id; }
+
} // namespace
+static constexpr char kInstanceDir[] = "instance_dir";
+CuttlefishConfig::MutableInstanceSpecific::MutableInstanceSpecific(
+ CuttlefishConfig* config, const std::string& id)
+ : config_(config), id_(id) {
+ // Legacy for acloud
+ (*Dictionary())[kInstanceDir] = config_->InstancesPath(IdToName(id));
+}
+
Json::Value* CuttlefishConfig::MutableInstanceSpecific::Dictionary() {
return &(*config_->dictionary_)[kInstances][id_];
}
@@ -36,13 +46,8 @@
return &(*config_->dictionary_)[kInstances][id_];
}
-static constexpr char kInstanceDir[] = "instance_dir";
std::string CuttlefishConfig::InstanceSpecific::instance_dir() const {
- return (*Dictionary())[kInstanceDir].asString();
-}
-void CuttlefishConfig::MutableInstanceSpecific::set_instance_dir(
- const std::string& instance_dir) {
- (*Dictionary())[kInstanceDir] = instance_dir;
+ return config_->InstancesPath(IdToName(id_));
}
std::string CuttlefishConfig::InstanceSpecific::instance_internal_dir() const {
@@ -131,6 +136,10 @@
return AbsolutePath(PerInstancePath("access-kregistry"));
}
+std::string CuttlefishConfig::InstanceSpecific::hwcomposer_pmem_path() const {
+ return AbsolutePath(PerInstancePath("hwcomposer-pmem"));
+}
+
std::string CuttlefishConfig::InstanceSpecific::pstore_path() const {
return AbsolutePath(PerInstancePath("pstore"));
}
@@ -140,7 +149,7 @@
}
std::string CuttlefishConfig::InstanceSpecific::logcat_path() const {
- return AbsolutePath(PerInstancePath("logcat"));
+ return AbsolutePath(PerInstanceLogPath("logcat"));
}
std::string CuttlefishConfig::InstanceSpecific::launcher_monitor_socket_path()
@@ -158,28 +167,24 @@
}
std::string CuttlefishConfig::InstanceSpecific::launcher_log_path() const {
- return AbsolutePath(PerInstancePath("launcher.log"));
+ return AbsolutePath(PerInstanceLogPath("launcher.log"));
}
std::string CuttlefishConfig::InstanceSpecific::sdcard_path() const {
return AbsolutePath(PerInstancePath("sdcard.img"));
}
-std::string CuttlefishConfig::InstanceSpecific::os_composite_disk_path() const {
- return AbsolutePath(PerInstancePath("os_composite.img"));
-}
-
std::string CuttlefishConfig::InstanceSpecific::persistent_composite_disk_path()
const {
return AbsolutePath(PerInstancePath("persistent_composite.img"));
}
-std::string CuttlefishConfig::InstanceSpecific::uboot_env_image_path() const {
- return AbsolutePath(PerInstancePath("uboot_env.img"));
+std::string CuttlefishConfig::InstanceSpecific::vbmeta_path() const {
+ return AbsolutePath(PerInstancePath("vbmeta.img"));
}
-std::string CuttlefishConfig::InstanceSpecific::vendor_boot_image_path() const {
- return AbsolutePath(PerInstancePath("vendor_boot_repacked.img"));
+std::string CuttlefishConfig::InstanceSpecific::uboot_env_image_path() const {
+ return AbsolutePath(PerInstancePath("uboot_env.img"));
}
static constexpr char kMobileBridgeName[] = "mobile_bridge_name";
@@ -205,9 +210,14 @@
(*Dictionary())[kMobileTapName] = mobile_tap_name;
}
-std::string CuttlefishConfig::InstanceSpecific::confui_hal_guest_socket_path()
- const {
- return PerInstanceInternalPath("confui_mock_hal_guest.sock");
+static constexpr char kConfUiHostPort[] = "confirmation_ui_host_port";
+int CuttlefishConfig::InstanceSpecific::confui_host_vsock_port() const {
+ return (*Dictionary())[kConfUiHostPort].asInt();
+}
+
+void CuttlefishConfig::MutableInstanceSpecific::set_confui_host_vsock_port(
+ int port) {
+ (*Dictionary())[kConfUiHostPort] = port;
}
static constexpr char kWifiTapName[] = "wifi_tap_name";
@@ -263,12 +273,21 @@
(*Dictionary())[kUuid] = uuid;
}
-static constexpr char kHostPort[] = "host_port";
-int CuttlefishConfig::InstanceSpecific::host_port() const {
+static constexpr char kHostPort[] = "adb_host_port";
+int CuttlefishConfig::InstanceSpecific::adb_host_port() const {
return (*Dictionary())[kHostPort].asInt();
}
-void CuttlefishConfig::MutableInstanceSpecific::set_host_port(int host_port) {
- (*Dictionary())[kHostPort] = host_port;
+void CuttlefishConfig::MutableInstanceSpecific::set_adb_host_port(int port) {
+ (*Dictionary())[kHostPort] = port;
+}
+
+static constexpr char kModemSimulatorId[] = "modem_simulator_host_id";
+int CuttlefishConfig::InstanceSpecific::modem_simulator_host_id() const {
+ return (*Dictionary())[kModemSimulatorId].asInt();
+}
+void CuttlefishConfig::MutableInstanceSpecific::set_modem_simulator_host_id(
+ int id) {
+ (*Dictionary())[kModemSimulatorId] = id;
}
static constexpr char kAdbIPAndPort[] = "adb_ip_and_port";
@@ -288,29 +307,13 @@
return "NO_ADB_MODE_SET_NO_VALID_DEVICE_NAME";
}
-static constexpr char kDeviceTitle[] = "device_title";
-std::string CuttlefishConfig::InstanceSpecific::device_title() const {
- return (*Dictionary())[kDeviceTitle].asString();
+static constexpr char kQemuVncServerPort[] = "qemu_vnc_server_port";
+int CuttlefishConfig::InstanceSpecific::qemu_vnc_server_port() const {
+ return (*Dictionary())[kQemuVncServerPort].asInt();
}
-void CuttlefishConfig::MutableInstanceSpecific::set_device_title(
- const std::string& title) {
- (*Dictionary())[kDeviceTitle] = title;
-}
-
-static constexpr char kVncServerPort[] = "vnc_server_port";
-int CuttlefishConfig::InstanceSpecific::vnc_server_port() const {
- return (*Dictionary())[kVncServerPort].asInt();
-}
-void CuttlefishConfig::MutableInstanceSpecific::set_vnc_server_port(int vnc_server_port) {
- (*Dictionary())[kVncServerPort] = vnc_server_port;
-}
-
-static constexpr char kFramesServerPort[] = "frames_server_port";
-int CuttlefishConfig::InstanceSpecific::frames_server_port() const {
- return (*Dictionary())[kFramesServerPort].asInt();
-}
-void CuttlefishConfig::MutableInstanceSpecific::set_frames_server_port(int frames_server_port) {
- (*Dictionary())[kFramesServerPort] = frames_server_port;
+void CuttlefishConfig::MutableInstanceSpecific::set_qemu_vnc_server_port(
+ int qemu_vnc_server_port) {
+ (*Dictionary())[kQemuVncServerPort] = qemu_vnc_server_port;
}
static constexpr char kTouchServerPort[] = "touch_server_port";
@@ -362,33 +365,6 @@
(*Dictionary())[kConfigServerPort] = config_server_port;
}
-static constexpr char kRootcanalHciPort[] = "rootcanal_hci_port";
-int CuttlefishConfig::InstanceSpecific::rootcanal_hci_port() const {
- return (*Dictionary())[kRootcanalHciPort].asInt();
-}
-void CuttlefishConfig::MutableInstanceSpecific::set_rootcanal_hci_port(
- int rootcanal_hci_port) {
- (*Dictionary())[kRootcanalHciPort] = rootcanal_hci_port;
-}
-
-static constexpr char kRootcanalLinkPort[] = "rootcanal_link_port";
-int CuttlefishConfig::InstanceSpecific::rootcanal_link_port() const {
- return (*Dictionary())[kRootcanalLinkPort].asInt();
-}
-void CuttlefishConfig::MutableInstanceSpecific::set_rootcanal_link_port(
- int rootcanal_link_port) {
- (*Dictionary())[kRootcanalLinkPort] = rootcanal_link_port;
-}
-
-static constexpr char kRootcanalTestPort[] = "rootcanal_test_port";
-int CuttlefishConfig::InstanceSpecific::rootcanal_test_port() const {
- return (*Dictionary())[kRootcanalTestPort].asInt();
-}
-void CuttlefishConfig::MutableInstanceSpecific::set_rootcanal_test_port(
- int rootcanal_test_port) {
- (*Dictionary())[kRootcanalTestPort] = rootcanal_test_port;
-}
-
static constexpr char kCameraServerPort[] = "camera_server_port";
int CuttlefishConfig::InstanceSpecific::camera_server_port() const {
return (*Dictionary())[kCameraServerPort].asInt();
@@ -398,29 +374,6 @@
(*Dictionary())[kCameraServerPort] = camera_server_port;
}
-static constexpr char kRootcanalConfigFile[] = "rootcanal_config_file";
-std::string CuttlefishConfig::InstanceSpecific::rootcanal_config_file() const {
- return (*Dictionary())[kRootcanalConfigFile].asString();
-}
-void CuttlefishConfig::MutableInstanceSpecific::set_rootcanal_config_file(
- const std::string& rootcanal_config_file) {
- (*Dictionary())[kRootcanalConfigFile] =
- DefaultHostArtifactsPath(rootcanal_config_file);
-}
-
-static constexpr char kRootcanalDefaultCommandsFile[] =
- "rootcanal_default_commands_file";
-std::string
-CuttlefishConfig::InstanceSpecific::rootcanal_default_commands_file() const {
- return (*Dictionary())[kRootcanalDefaultCommandsFile].asString();
-}
-void CuttlefishConfig::MutableInstanceSpecific::
- set_rootcanal_default_commands_file(
- const std::string& rootcanal_default_commands_file) {
- (*Dictionary())[kRootcanalDefaultCommandsFile] =
- DefaultHostArtifactsPath(rootcanal_default_commands_file);
-}
-
static constexpr char kWebrtcDeviceId[] = "webrtc_device_id";
void CuttlefishConfig::MutableInstanceSpecific::set_webrtc_device_id(
const std::string& id) {
@@ -438,6 +391,40 @@
return (*Dictionary())[kStartSigServer].asBool();
}
+static constexpr char kStartSigServerProxy[] = "webrtc_start_sig_server_proxy";
+void CuttlefishConfig::MutableInstanceSpecific::
+ set_start_webrtc_sig_server_proxy(bool start) {
+ (*Dictionary())[kStartSigServerProxy] = start;
+}
+bool CuttlefishConfig::InstanceSpecific::start_webrtc_sig_server_proxy() const {
+ return (*Dictionary())[kStartSigServerProxy].asBool();
+}
+
+static constexpr char kStartWmediumd[] = "start_wmediumd";
+void CuttlefishConfig::MutableInstanceSpecific::set_start_wmediumd(bool start) {
+ (*Dictionary())[kStartWmediumd] = start;
+}
+bool CuttlefishConfig::InstanceSpecific::start_wmediumd() const {
+ return (*Dictionary())[kStartWmediumd].asBool();
+}
+
+static constexpr char kStartRootcanal[] = "start_rootcanal";
+void CuttlefishConfig::MutableInstanceSpecific::set_start_rootcanal(
+ bool start) {
+ (*Dictionary())[kStartRootcanal] = start;
+}
+bool CuttlefishConfig::InstanceSpecific::start_rootcanal() const {
+ return (*Dictionary())[kStartRootcanal].asBool();
+}
+
+static constexpr char kStartAp[] = "start_ap";
+void CuttlefishConfig::MutableInstanceSpecific::set_start_ap(bool start) {
+ (*Dictionary())[kStartAp] = start;
+}
+bool CuttlefishConfig::InstanceSpecific::start_ap() const {
+ return (*Dictionary())[kStartAp].asBool();
+}
+
std::string CuttlefishConfig::InstanceSpecific::touch_socket_path(
int screen_idx) const {
return PerInstanceInternalPath(
@@ -456,26 +443,13 @@
return PerInstanceInternalPath("frames.sock");
}
-static constexpr char kWifiMacAddress[] = "wifi_mac_address";
-void CuttlefishConfig::MutableInstanceSpecific::set_wifi_mac_address(
- const std::array<unsigned char, 6>& mac_address) {
- Json::Value mac_address_obj(Json::arrayValue);
- for (const auto& num : mac_address) {
- mac_address_obj.append(num);
- }
- (*Dictionary())[kWifiMacAddress] = mac_address_obj;
+static constexpr char kWifiMacPrefix[] = "wifi_mac_prefix";
+int CuttlefishConfig::InstanceSpecific::wifi_mac_prefix() const {
+ return (*Dictionary())[kWifiMacPrefix].asInt();
}
-std::array<unsigned char, 6> CuttlefishConfig::InstanceSpecific::wifi_mac_address() const {
- std::array<unsigned char, 6> mac_address{0, 0, 0, 0, 0, 0};
- auto mac_address_obj = (*Dictionary())[kWifiMacAddress];
- if (mac_address_obj.size() != 6) {
- LOG(ERROR) << kWifiMacAddress << " entry had wrong size";
- return {};
- }
- for (int i = 0; i < 6; i++) {
- mac_address[i] = mac_address_obj[i].asInt();
- }
- return mac_address;
+void CuttlefishConfig::MutableInstanceSpecific::set_wifi_mac_prefix(
+ int wifi_mac_prefix) {
+ (*Dictionary())[kWifiMacPrefix] = wifi_mac_prefix;
}
std::string CuttlefishConfig::InstanceSpecific::factory_reset_protected_path() const {
@@ -502,8 +476,20 @@
return PerInstancePath(relative_path.c_str());
}
-std::string CuttlefishConfig::InstanceSpecific::instance_name() const {
- return "cvd-" + id_;
+std::string CuttlefishConfig::InstanceSpecific::PerInstanceLogPath(
+ const std::string& file_name) const {
+ if (file_name.size() == 0) {
+ // Don't append a / if file_name is empty.
+ return PerInstancePath(kLogDirName);
+ }
+ auto relative_path = (std::string(kLogDirName) + "/") + file_name;
+ return PerInstancePath(relative_path.c_str());
}
+std::string CuttlefishConfig::InstanceSpecific::instance_name() const {
+ return IdToName(id_);
+}
+
+std::string CuttlefishConfig::InstanceSpecific::id() const { return id_; }
+
} // namespace cuttlefish
diff --git a/host/libs/config/data_image.cpp b/host/libs/config/data_image.cpp
index 044dadb..8932c7c 100644
--- a/host/libs/config/data_image.cpp
+++ b/host/libs/config/data_image.cpp
@@ -1,13 +1,16 @@
#include "host/libs/config/data_image.h"
#include <android-base/logging.h>
+#include <android-base/result.h>
+
+#include "blkid.h"
#include "common/libs/fs/shared_buf.h"
-
#include "common/libs/utils/files.h"
+#include "common/libs/utils/result.h"
#include "common/libs/utils/subprocess.h"
-
#include "host/libs/config/mbr.h"
+#include "host/libs/vm_manager/gem5_manager.h"
namespace cuttlefish {
@@ -20,18 +23,77 @@
const int FSCK_ERROR_CORRECTED = 1;
const int FSCK_ERROR_CORRECTED_REQUIRES_REBOOT = 2;
-bool ForceFsckImage(const char* data_image) {
- auto fsck_path = HostBinaryPath("fsck.f2fs");
+// Currently the Cuttlefish bootloaders are built only for x86 (32-bit),
+// ARM (QEMU only, 32-bit) and AArch64 (64-bit), and U-Boot will hard-code
+// these search paths. Install all bootloaders to one of these paths.
+// NOTE: For now, just ignore the 32-bit ARM version, as Debian doesn't
+// build an EFI monolith for this architecture.
+const std::string kBootPathIA32 = "EFI/BOOT/BOOTIA32.EFI";
+const std::string kBootPathAA64 = "EFI/BOOT/BOOTAA64.EFI";
+const std::string kM5 = "";
+
+// These are the paths Debian installs the monoliths to. If another distro
+// uses an alternative monolith path, add it to this table
+const std::pair<std::string, std::string> kGrubBlobTable[] = {
+ {"/usr/lib/grub/i386-efi/monolithic/grubia32.efi", kBootPathIA32},
+ {"/usr/lib/grub/arm64-efi/monolithic/grubaa64.efi", kBootPathAA64},
+};
+
+// M5 checkpoint required binary file
+const std::pair<std::string, std::string> kM5BlobTable[] = {
+ {"/tmp/m5", kM5},
+};
+
+bool ForceFsckImage(const CuttlefishConfig& config,
+ const std::string& data_image) {
+ std::string fsck_path;
+ if (config.userdata_format() == "f2fs") {
+ fsck_path = HostBinaryPath("fsck.f2fs");
+ } else if (config.userdata_format() == "ext4") {
+ fsck_path = "/sbin/e2fsck";
+ }
int fsck_status = execute({fsck_path, "-y", "-f", data_image});
if (fsck_status & ~(FSCK_ERROR_CORRECTED|FSCK_ERROR_CORRECTED_REQUIRES_REBOOT)) {
- LOG(ERROR) << "`fsck.f2fs -y -f " << data_image << "` failed with code "
+ LOG(ERROR) << "`" << fsck_path << " -y -f " << data_image << "` failed with code "
<< fsck_status;
return false;
}
return true;
}
-bool ResizeImage(const char* data_image, int data_image_mb) {
+bool NewfsMsdos(const std::string& data_image, int data_image_mb,
+ int offset_num_mb) {
+ off_t image_size_bytes = static_cast<off_t>(data_image_mb) << 20;
+ off_t offset_size_bytes = static_cast<off_t>(offset_num_mb) << 20;
+ image_size_bytes -= offset_size_bytes;
+ off_t image_size_sectors = image_size_bytes / 512;
+ auto newfs_msdos_path = HostBinaryPath("newfs_msdos");
+ return execute({newfs_msdos_path,
+ "-F",
+ "32",
+ "-m",
+ "0xf8",
+ "-o",
+ "0",
+ "-c",
+ "8",
+ "-h",
+ "255",
+ "-u",
+ "63",
+ "-S",
+ "512",
+ "-s",
+ std::to_string(image_size_sectors),
+ "-C",
+ std::to_string(data_image_mb) + "M",
+ "-@",
+ std::to_string(offset_size_bytes),
+ data_image}) == 0;
+}
+
+bool ResizeImage(const CuttlefishConfig& config, const std::string& data_image,
+ int data_image_mb) {
auto file_mb = FileSize(data_image) >> 20;
if (file_mb > data_image_mb) {
LOG(ERROR) << data_image << " is already " << file_mb << " MB, will not "
@@ -48,18 +110,23 @@
<< data_image << "` failed:" << fd->StrError();
return false;
}
- bool fsck_success = ForceFsckImage(data_image);
+ bool fsck_success = ForceFsckImage(config, data_image);
if (!fsck_success) {
return false;
}
- auto resize_path = HostBinaryPath("resize.f2fs");
+ std::string resize_path;
+ if (config.userdata_format() == "f2fs") {
+ resize_path = HostBinaryPath("resize.f2fs");
+ } else if (config.userdata_format() == "ext4") {
+ resize_path = "/sbin/resize2fs";
+ }
int resize_status = execute({resize_path, data_image});
if (resize_status != 0) {
- LOG(ERROR) << "`resize.f2fs " << data_image << "` failed with code "
+ LOG(ERROR) << "`" << resize_path << " " << data_image << "` failed with code "
<< resize_status;
return false;
}
- fsck_success = ForceFsckImage(data_image);
+ fsck_success = ForceFsckImage(config, data_image);
if (!fsck_success) {
return false;
}
@@ -68,7 +135,7 @@
}
} // namespace
-void CreateBlankImage(
+bool CreateBlankImage(
const std::string& image, int num_mb, const std::string& image_fmt) {
LOG(DEBUG) << "Creating " << image;
@@ -80,124 +147,420 @@
if (fd->Truncate(image_size_bytes) != 0) {
LOG(ERROR) << "`truncate --size=" << num_mb << "M " << image
<< "` failed:" << fd->StrError();
- return;
+ return false;
}
}
if (image_fmt == "ext4") {
- execute({"/sbin/mkfs.ext4", image});
+ if (execute({"/sbin/mkfs.ext4", image}) != 0) {
+ return false;
+ }
} else if (image_fmt == "f2fs") {
auto make_f2fs_path = cuttlefish::HostBinaryPath("make_f2fs");
- execute({make_f2fs_path, "-t", image_fmt, image, "-C", "utf8", "-O",
- "compression,extra_attr,prjquota", "-g", "android"});
+ if (execute({make_f2fs_path, "-t", image_fmt, image, "-C", "utf8", "-O",
+ "compression,extra_attr,project_quota", "-g", "android"}) != 0) {
+ return false;
+ }
} else if (image_fmt == "sdcard") {
// Reserve 1MB in the image for the MBR and padding, to simulate what
// other OSes do by default when partitioning a drive
off_t offset_size_bytes = 1 << 20;
image_size_bytes -= offset_size_bytes;
- off_t image_size_sectors = image_size_bytes / 512;
- auto newfs_msdos_path = HostBinaryPath("newfs_msdos");
- execute({newfs_msdos_path, "-F", "32", "-m", "0xf8", "-a", "4088",
- "-o", "0", "-c", "8", "-h", "255",
- "-u", "63", "-S", "512",
- "-s", std::to_string(image_size_sectors),
- "-C", std::to_string(num_mb) + "M",
- "-@", std::to_string(offset_size_bytes),
- image});
+ if (!NewfsMsdos(image, num_mb, 1)) {
+ LOG(ERROR) << "Failed to create SD-Card filesystem";
+ return false;
+ }
// Write the MBR after the filesystem is formatted, as the formatting tools
// don't consistently preserve the image contents
MasterBootRecord mbr = {
- .partitions = {{
- .partition_type = 0xC,
- .first_lba = (std::uint32_t) offset_size_bytes / SECTOR_SIZE,
- .num_sectors = (std::uint32_t) image_size_bytes / SECTOR_SIZE,
- }},
- .boot_signature = { 0x55, 0xAA },
+ .partitions = {{
+ .partition_type = 0xC,
+ .first_lba = (std::uint32_t) offset_size_bytes / SECTOR_SIZE,
+ .num_sectors = (std::uint32_t) image_size_bytes / SECTOR_SIZE,
+ }},
+ .boot_signature = {0x55, 0xAA},
};
auto fd = SharedFD::Open(image, O_RDWR);
if (WriteAllBinary(fd, &mbr) != sizeof(MasterBootRecord)) {
LOG(ERROR) << "Writing MBR to " << image << " failed:" << fd->StrError();
- return;
+ return false;
}
} else if (image_fmt != "none") {
LOG(WARNING) << "Unknown image format '" << image_fmt
<< "' for " << image << ", treating as 'none'.";
}
+ return true;
}
-DataImageResult ApplyDataImagePolicy(const CuttlefishConfig& config,
- const std::string& data_image) {
- bool data_exists = FileHasContent(data_image.c_str());
- bool remove{};
- bool create{};
- bool resize{};
-
- if (config.data_policy() == kDataPolicyUseExisting) {
- if (!data_exists) {
- LOG(ERROR) << "Specified data image file does not exists: " << data_image;
- return DataImageResult::Error;
- }
- if (config.blank_data_image_mb() > 0) {
- LOG(ERROR) << "You should NOT use -blank_data_image_mb with -data_policy="
- << kDataPolicyUseExisting;
- return DataImageResult::Error;
- }
- create = false;
- remove = false;
- resize = false;
- } else if (config.data_policy() == kDataPolicyAlwaysCreate) {
- remove = data_exists;
- create = true;
- resize = false;
- } else if (config.data_policy() == kDataPolicyCreateIfMissing) {
- create = !data_exists;
- remove = false;
- resize = false;
- } else if (config.data_policy() == kDataPolicyResizeUpTo) {
- create = false;
- remove = false;
- resize = true;
- } else {
- LOG(ERROR) << "Invalid data_policy: " << config.data_policy();
- return DataImageResult::Error;
+std::string GetFsType(const std::string& path) {
+ std::string fs_type;
+ blkid_cache cache;
+ if (blkid_get_cache(&cache, NULL) < 0) {
+ LOG(INFO) << "blkid_get_cache failed";
+ return fs_type;
+ }
+ blkid_dev dev = blkid_get_dev(cache, path.c_str(), BLKID_DEV_NORMAL);
+ if (!dev) {
+ LOG(INFO) << "blkid_get_dev failed";
+ blkid_put_cache(cache);
+ return fs_type;
}
- if (remove) {
- RemoveFile(data_image.c_str());
- }
-
- if (create) {
- if (config.blank_data_image_mb() <= 0) {
- LOG(ERROR) << "-blank_data_image_mb is required to create data image";
- return DataImageResult::Error;
+ const char *type, *value;
+ blkid_tag_iterate iter = blkid_tag_iterate_begin(dev);
+ while (blkid_tag_next(iter, &type, &value) == 0) {
+ if (!strcmp(type, "TYPE")) {
+ fs_type = value;
}
- CreateBlankImage(data_image.c_str(), config.blank_data_image_mb(),
- config.blank_data_image_fmt());
- return DataImageResult::FileUpdated;
- } else if (resize) {
- if (!data_exists) {
- LOG(ERROR) << data_image << " does not exist, but resizing was requested";
- return DataImageResult::Error;
- }
- bool success = ResizeImage(data_image.c_str(), config.blank_data_image_mb());
- return success ? DataImageResult::FileUpdated : DataImageResult::Error;
- } else {
- LOG(DEBUG) << data_image << " exists. Not creating it.";
- return DataImageResult::NoChange;
}
+ blkid_tag_iterate_end(iter);
+ blkid_put_cache(cache);
+ return fs_type;
}
-bool InitializeMiscImage(const std::string& misc_image) {
- bool misc_exists = FileHasContent(misc_image.c_str());
+struct DataImageTag {};
- if (misc_exists) {
- LOG(DEBUG) << "misc partition image: use existing";
+class FixedDataImagePath : public DataImagePath {
+ public:
+ INJECT(FixedDataImagePath(ANNOTATED(DataImageTag, std::string) path))
+ : path_(path) {}
+
+ const std::string& Path() const override { return path_; }
+
+ private:
+ std::string path_;
+};
+
+fruit::Component<DataImagePath> FixedDataImagePathComponent(
+ const std::string* path) {
+ return fruit::createComponent()
+ .bind<DataImagePath, FixedDataImagePath>()
+ .bindInstance<fruit::Annotated<DataImageTag, std::string>>(*path);
+}
+
+class InitializeDataImageImpl : public InitializeDataImage {
+ public:
+ INJECT(InitializeDataImageImpl(const CuttlefishConfig& config,
+ DataImagePath& data_path))
+ : config_(config), data_path_(data_path) {}
+
+ // SetupFeature
+ std::string Name() const override { return "InitializeDataImageImpl"; }
+ bool Enabled() const override { return true; }
+
+ private:
+ std::unordered_set<SetupFeature*> Dependencies() const override { return {}; }
+ bool Setup() override {
+ auto action = ChooseAction();
+ if (!action.ok()) {
+ LOG(ERROR) << "Failed to select a userdata processing action: "
+ << action.error();
+ return false;
+ }
+ auto result = EvaluateAction(*action);
+ if (!result.ok()) {
+ LOG(ERROR) << "Failed to evaluate userdata action: " << result.error();
+ return false;
+ }
return true;
}
- LOG(DEBUG) << "misc partition image: creating empty";
- CreateBlankImage(misc_image, 1 /* mb */, "none");
- return true;
+ private:
+ enum class DataImageAction { kNoAction, kCreateImage, kResizeImage };
+
+ Result<DataImageAction> ChooseAction() {
+ if (config_.data_policy() == kDataPolicyAlwaysCreate) {
+ return DataImageAction::kCreateImage;
+ }
+ if (!FileHasContent(data_path_.Path())) {
+ if (config_.data_policy() == kDataPolicyUseExisting) {
+ return CF_ERR("A data image must exist to use -data_policy="
+ << kDataPolicyUseExisting);
+ } else if (config_.data_policy() == kDataPolicyResizeUpTo) {
+ return CF_ERR(data_path_.Path()
+ << " does not exist, but resizing was requested");
+ }
+ return DataImageAction::kCreateImage;
+ }
+ if (GetFsType(data_path_.Path()) != config_.userdata_format()) {
+ CF_EXPECT(config_.data_policy() == kDataPolicyResizeUpTo,
+ "Changing the fs format is incompatible with -data_policy="
+ << kDataPolicyResizeUpTo);
+ return DataImageAction::kCreateImage;
+ }
+ if (config_.data_policy() == kDataPolicyResizeUpTo) {
+ return DataImageAction::kResizeImage;
+ }
+ return DataImageAction::kNoAction;
+ }
+
+ Result<void> EvaluateAction(DataImageAction action) {
+ switch (action) {
+ case DataImageAction::kNoAction:
+ LOG(DEBUG) << data_path_.Path() << " exists. Not creating it.";
+ return {};
+ case DataImageAction::kCreateImage: {
+ RemoveFile(data_path_.Path());
+ CF_EXPECT(config_.blank_data_image_mb() != 0,
+ "Expected `-blank_data_image_mb` to be set for "
+ "image creation.");
+ CF_EXPECT(
+ CreateBlankImage(data_path_.Path(), config_.blank_data_image_mb(),
+ config_.userdata_format()),
+ "Failed to create a blank image at \""
+ << data_path_.Path() << "\" with size "
+ << config_.blank_data_image_mb() << " and format \""
+ << config_.userdata_format() << "\"");
+ return {};
+ }
+ case DataImageAction::kResizeImage: {
+ CF_EXPECT(config_.blank_data_image_mb() != 0,
+ "Expected `-blank_data_image_mb` to be set for "
+ "image resizing.");
+ CF_EXPECT(ResizeImage(config_, data_path_.Path(),
+ config_.blank_data_image_mb()),
+ "Failed to resize \"" << data_path_.Path() << "\" to "
+ << config_.blank_data_image_mb()
+ << " MB");
+ return {};
+ }
+ }
+ }
+
+ const CuttlefishConfig& config_;
+ DataImagePath& data_path_;
+};
+
+fruit::Component<fruit::Required<const CuttlefishConfig, DataImagePath>,
+ InitializeDataImage>
+InitializeDataImageComponent() {
+ return fruit::createComponent()
+ .addMultibinding<SetupFeature, InitializeDataImage>()
+ .bind<InitializeDataImage, InitializeDataImageImpl>();
+}
+
+struct MiscImageTag {};
+
+class FixedMiscImagePath : public MiscImagePath {
+ public:
+ INJECT(FixedMiscImagePath(ANNOTATED(MiscImageTag, std::string) path))
+ : path_(path) {}
+
+ const std::string& Path() const override { return path_; }
+
+ private:
+ std::string path_;
+};
+
+class InitializeMiscImageImpl : public InitializeMiscImage {
+ public:
+ INJECT(InitializeMiscImageImpl(MiscImagePath& misc_path))
+ : misc_path_(misc_path) {}
+
+ // SetupFeature
+ std::string Name() const override { return "InitializeMiscImageImpl"; }
+ bool Enabled() const override { return true; }
+
+ private:
+ std::unordered_set<SetupFeature*> Dependencies() const override { return {}; }
+ bool Setup() override {
+ bool misc_exists = FileHasContent(misc_path_.Path());
+
+ if (misc_exists) {
+ LOG(DEBUG) << "misc partition image: use existing at \""
+ << misc_path_.Path() << "\"";
+ return true;
+ }
+
+ LOG(DEBUG) << "misc partition image: creating empty at \""
+ << misc_path_.Path() << "\"";
+ if (!CreateBlankImage(misc_path_.Path(), 1 /* mb */, "none")) {
+ LOG(ERROR) << "Failed to create misc image";
+ return false;
+ }
+ return true;
+ }
+
+ private:
+ MiscImagePath& misc_path_;
+};
+
+fruit::Component<MiscImagePath> FixedMiscImagePathComponent(
+ const std::string* path) {
+ return fruit::createComponent()
+ .bind<MiscImagePath, FixedMiscImagePath>()
+ .bindInstance<fruit::Annotated<MiscImageTag, std::string>>(*path);
+}
+
+fruit::Component<fruit::Required<MiscImagePath>, InitializeMiscImage>
+InitializeMiscImageComponent() {
+ return fruit::createComponent()
+ .addMultibinding<SetupFeature, InitializeMiscImage>()
+ .bind<InitializeMiscImage, InitializeMiscImageImpl>();
+}
+
+struct EspImageTag {};
+struct KernelPathTag {};
+struct InitRamFsTag {};
+struct RootFsTag {};
+struct ConfigTag {};
+
+class InitializeEspImageImpl : public InitializeEspImage {
+ public:
+ INJECT(InitializeEspImageImpl(ANNOTATED(EspImageTag, std::string) esp_image,
+ ANNOTATED(KernelPathTag, std::string)
+ kernel_path,
+ ANNOTATED(InitRamFsTag, std::string)
+ initramfs_path,
+ ANNOTATED(RootFsTag, std::string) rootfs_path,
+ ANNOTATED(ConfigTag, const CuttlefishConfig *) config))
+ : esp_image_(esp_image),
+ kernel_path_(kernel_path),
+ initramfs_path_(initramfs_path),
+ rootfs_path_(rootfs_path),
+ config_(config){}
+
+ // SetupFeature
+ std::string Name() const override { return "InitializeEspImageImpl"; }
+ std::unordered_set<SetupFeature*> Dependencies() const override { return {}; }
+ bool Enabled() const override { return !rootfs_path_.empty(); }
+
+ protected:
+ bool Setup() override {
+ bool esp_exists = FileHasContent(esp_image_);
+ if (esp_exists) {
+ LOG(DEBUG) << "esp partition image: use existing";
+ return true;
+ }
+
+ LOG(DEBUG) << "esp partition image: creating default";
+
+ // newfs_msdos won't make a partition smaller than 257 mb
+ // this should be enough for anybody..
+ auto tmp_esp_image = esp_image_ + ".tmp";
+ if (!NewfsMsdos(tmp_esp_image, 257 /* mb */, 0 /* mb (offset) */)) {
+ LOG(ERROR) << "Failed to create filesystem for " << tmp_esp_image;
+ return false;
+ }
+
+ // For licensing and build reproducibility reasons, pick up the bootloaders
+ // from the host Linux distribution (if present) and pack them into the
+ // automatically generated ESP. If the user wants their own bootloaders,
+ // they can use -esp_image=/path/to/esp.img to override, so we don't need
+ // to accommodate customizations of this packing process.
+
+ int success;
+ const std::pair<std::string, std::string> *kBlobTable;
+ std::size_t size;
+ // Skip GRUB on Gem5
+ if (config_->vm_manager() != vm_manager::Gem5Manager::name()){
+ // Currently we only support Debian based distributions, and GRUB is built
+ // for those distros to always load grub.cfg from EFI/debian/grub.cfg, and
+ // nowhere else. If you want to add support for other distros, make the
+ // extra directories below and copy the initial grub.cfg there as well
+ auto mmd = HostBinaryPath("mmd");
+ success =
+ execute({mmd, "-i", tmp_esp_image, "EFI", "EFI/BOOT", "EFI/debian"});
+ if (success != 0) {
+ LOG(ERROR) << "Failed to create directories in " << tmp_esp_image;
+ return false;
+ }
+ size = sizeof(kGrubBlobTable)/sizeof(const std::pair<std::string, std::string>);
+ kBlobTable = kGrubBlobTable;
+ } else {
+ size = sizeof(kM5BlobTable)/sizeof(const std::pair<std::string, std::string>);
+ kBlobTable = kM5BlobTable;
+ }
+
+ // The grub binaries are small, so just copy all the architecture blobs
+ // we can find, which minimizes complexity. If the user removed the grub bin
+ // package from their system, the ESP will be empty and Other OS will not be
+ // supported
+ auto mcopy = HostBinaryPath("mcopy");
+ bool copied = false;
+ for (int i=0; i<size; i++) {
+ auto grub = kBlobTable[i];
+ if (!FileExists(grub.first)) {
+ continue;
+ }
+ success = execute({mcopy, "-o", "-i", tmp_esp_image, "-s", grub.first,
+ "::" + grub.second});
+ if (success != 0) {
+ LOG(ERROR) << "Failed to copy " << grub.first << " to " << grub.second
+ << " in " << tmp_esp_image;
+ return false;
+ }
+ copied = true;
+ }
+
+ if (!copied) {
+ LOG(ERROR) << "Binary dependencies were not found on this system; Other OS "
+ "support will be broken";
+ return false;
+ }
+
+ // Skip Gem5 case. Gem5 will never be able to use bootloaders like grub.
+ if (config_->vm_manager() != vm_manager::Gem5Manager::name()){
+ auto grub_cfg = DefaultHostArtifactsPath("etc/grub/grub.cfg");
+ CHECK(FileExists(grub_cfg)) << "Missing file " << grub_cfg << "!";
+ success =
+ execute({mcopy, "-i", tmp_esp_image, "-s", grub_cfg, "::EFI/debian/"});
+ if (success != 0) {
+ LOG(ERROR) << "Failed to copy " << grub_cfg << " to " << tmp_esp_image;
+ return false;
+ }
+ }
+
+ if (!kernel_path_.empty()) {
+ success = execute(
+ {mcopy, "-i", tmp_esp_image, "-s", kernel_path_, "::vmlinuz"});
+ if (success != 0) {
+ LOG(ERROR) << "Failed to copy " << kernel_path_ << " to "
+ << tmp_esp_image;
+ return false;
+ }
+
+ if (!initramfs_path_.empty()) {
+ success = execute({mcopy, "-i", tmp_esp_image, "-s", initramfs_path_,
+ "::initrd.img"});
+ if (success != 0) {
+ LOG(ERROR) << "Failed to copy " << initramfs_path_ << " to "
+ << tmp_esp_image;
+ return false;
+ }
+ }
+ }
+
+ if (!cuttlefish::RenameFile(tmp_esp_image, esp_image_)) {
+ LOG(ERROR) << "Renaming " << tmp_esp_image << " to " << esp_image_
+ << " failed";
+ return false;
+ }
+ return true;
+ }
+
+ private:
+ std::string esp_image_;
+ std::string kernel_path_;
+ std::string initramfs_path_;
+ std::string rootfs_path_;
+ const CuttlefishConfig* config_;
+};
+
+fruit::Component<fruit::Required<const CuttlefishConfig>,
+ InitializeEspImage> InitializeEspImageComponent(
+ const std::string* esp_image, const std::string* kernel_path,
+ const std::string* initramfs_path, const std::string* rootfs_path,
+ const CuttlefishConfig* config) {
+ return fruit::createComponent()
+ .addMultibinding<SetupFeature, InitializeEspImage>()
+ .bind<InitializeEspImage, InitializeEspImageImpl>()
+ .bindInstance<fruit::Annotated<EspImageTag, std::string>>(*esp_image)
+ .bindInstance<fruit::Annotated<KernelPathTag, std::string>>(*kernel_path)
+ .bindInstance<fruit::Annotated<InitRamFsTag, std::string>>(
+ *initramfs_path)
+ .bindInstance<fruit::Annotated<RootFsTag, std::string>>(*rootfs_path)
+ .bindInstance<fruit::Annotated<ConfigTag, CuttlefishConfig>>(*config);
}
} // namespace cuttlefish
diff --git a/host/libs/config/data_image.h b/host/libs/config/data_image.h
index 6a6a810..0ae9fcc 100644
--- a/host/libs/config/data_image.h
+++ b/host/libs/config/data_image.h
@@ -1,21 +1,50 @@
#pragma once
#include <string>
+//
+#include <fruit/fruit.h>
#include "host/libs/config/cuttlefish_config.h"
+#include "host/libs/config/feature.h"
namespace cuttlefish {
-enum class DataImageResult {
- Error,
- NoChange,
- FileUpdated,
+class DataImagePath {
+ public:
+ virtual ~DataImagePath() = default;
+ virtual const std::string& Path() const = 0;
};
-DataImageResult ApplyDataImagePolicy(const CuttlefishConfig& config,
- const std::string& path);
-bool InitializeMiscImage(const std::string& misc_image);
-void CreateBlankImage(
+class InitializeDataImage : public SetupFeature {};
+
+fruit::Component<DataImagePath> FixedDataImagePathComponent(
+ const std::string* path);
+fruit::Component<fruit::Required<const CuttlefishConfig, DataImagePath>,
+ InitializeDataImage>
+InitializeDataImageComponent();
+
+class InitializeEspImage : public SetupFeature {};
+
+fruit::Component<fruit::Required<const CuttlefishConfig>,
+ InitializeEspImage> InitializeEspImageComponent(
+ const std::string* esp_image, const std::string* kernel_path,
+ const std::string* initramfs_path, const std::string* root_fs,
+ const CuttlefishConfig* config);
+
+bool CreateBlankImage(
const std::string& image, int num_mb, const std::string& image_fmt);
+class MiscImagePath {
+ public:
+ virtual ~MiscImagePath() = default;
+ virtual const std::string& Path() const = 0;
+};
+
+class InitializeMiscImage : public SetupFeature {};
+
+fruit::Component<MiscImagePath> FixedMiscImagePathComponent(
+ const std::string* path);
+fruit::Component<fruit::Required<MiscImagePath>, InitializeMiscImage>
+InitializeMiscImageComponent();
+
} // namespace cuttlefish
diff --git a/host/libs/config/feature.cpp b/host/libs/config/feature.cpp
new file mode 100644
index 0000000..cecf201
--- /dev/null
+++ b/host/libs/config/feature.cpp
@@ -0,0 +1,98 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "host/libs/config/feature.h"
+
+#include <unordered_set>
+
+#include "common/libs/utils/result.h"
+
+namespace cuttlefish {
+
+SetupFeature::~SetupFeature() {}
+
+Result<void> SetupFeature::ResultSetup() {
+ CF_EXPECT(Setup());
+ return {};
+}
+
+bool SetupFeature::Setup() {
+ LOG(ERROR) << "Missing ResultSetup implementation";
+ return false;
+}
+
+/* static */ Result<void> SetupFeature::RunSetup(
+ const std::vector<SetupFeature*>& features) {
+ std::unordered_set<SetupFeature*> enabled;
+ for (const auto& feature : features) {
+ CF_EXPECT(feature != nullptr, "Received null feature");
+ if (feature->Enabled()) {
+ enabled.insert(feature);
+ }
+ }
+ // Collect these in a vector first to trigger any obvious dependency issues.
+ std::vector<SetupFeature*> ordered_features;
+ auto add_feature = [&ordered_features](SetupFeature* feature) -> bool {
+ ordered_features.push_back(feature);
+ return true;
+ };
+ CF_EXPECT(Feature<SetupFeature>::TopologicalVisit(enabled, add_feature),
+ "Dependency issue detected, not performing any setup.");
+ // TODO(b/189153501): This can potentially be parallelized.
+ for (auto& feature : ordered_features) {
+ LOG(DEBUG) << "Running setup for " << feature->Name();
+ CF_EXPECT(feature->ResultSetup(), "Setup failed for " << feature->Name());
+ }
+ return {};
+}
+
+Result<void> FlagFeature::ProcessFlags(
+ const std::vector<FlagFeature*>& features,
+ std::vector<std::string>& flags) {
+ std::unordered_set<FlagFeature*> features_set(features.begin(),
+ features.end());
+ CF_EXPECT(features_set.count(nullptr) == 0, "Received null feature");
+ auto handle = [&flags](FlagFeature* feature) -> bool {
+ return feature->Process(flags);
+ };
+ CF_EXPECT(
+ Feature<FlagFeature>::TopologicalVisit(features_set, handle),
+ "Unable to parse flags.");
+ return {};
+}
+
+bool FlagFeature::WriteGflagsHelpXml(const std::vector<FlagFeature*>& features,
+ std::ostream& out) {
+ // Lifted from external/gflags/src/gflags_reporting.cc:ShowXMLOfFlags
+ out << "<?xml version=\"1.0\"?>\n";
+ out << "<AllFlags>\n";
+ out << " <program>program</program>\n";
+ out << " <usage>usage</usage>\n";
+ for (const auto& feature : features) {
+ if (!feature) {
+ LOG(ERROR) << "Received null feature";
+ return false;
+ }
+ if (!feature->WriteGflagsCompatHelpXml(out)) {
+ LOG(ERROR) << "Failure to write xml";
+ return false;
+ }
+ }
+ out << "</AllFlags>";
+ return true;
+}
+
+} // namespace cuttlefish
diff --git a/host/libs/config/feature.h b/host/libs/config/feature.h
new file mode 100644
index 0000000..15590e4
--- /dev/null
+++ b/host/libs/config/feature.h
@@ -0,0 +1,117 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#pragma once
+
+#include <ostream>
+#include <string>
+#include <unordered_map>
+#include <unordered_set>
+#include <vector>
+
+#include <android-base/logging.h>
+
+#include "common/libs/utils/result.h"
+
+namespace cuttlefish {
+
+template <typename Subclass>
+class Feature {
+ public:
+ virtual ~Feature() = default;
+
+ virtual std::string Name() const = 0;
+
+ static Result<void> TopologicalVisit(
+ const std::unordered_set<Subclass*>& features,
+ const std::function<bool(Subclass*)>& callback);
+
+ private:
+ virtual std::unordered_set<Subclass*> Dependencies() const = 0;
+};
+
+class SetupFeature : public virtual Feature<SetupFeature> {
+ public:
+ virtual ~SetupFeature();
+
+ static Result<void> RunSetup(const std::vector<SetupFeature*>& features);
+
+ virtual bool Enabled() const = 0;
+
+ private:
+ virtual Result<void> ResultSetup();
+ virtual bool Setup();
+};
+
+class FlagFeature : public Feature<FlagFeature> {
+ public:
+ static Result<void> ProcessFlags(const std::vector<FlagFeature*>& features,
+ std::vector<std::string>& flags);
+ static bool WriteGflagsHelpXml(const std::vector<FlagFeature*>& features,
+ std::ostream& out);
+
+ private:
+ // Must be executed in dependency order following Dependencies(). Expected to
+ // mutate the `flags` argument to remove handled flags, and possibly introduce
+ // new flag values (e.g. from a file).
+ virtual bool Process(std::vector<std::string>& flags) = 0;
+
+ // TODO(schuffelen): Migrate the xml help to human-readable help output after
+ // the gflags migration is done.
+
+ // Write an xml fragment that is compatible with gflags' `--helpxml` format.
+ virtual bool WriteGflagsCompatHelpXml(std::ostream& out) const = 0;
+};
+
+template <typename Subclass>
+Result<void> Feature<Subclass>::TopologicalVisit(
+ const std::unordered_set<Subclass*>& features,
+ const std::function<bool(Subclass*)>& callback) {
+ enum class Status { UNVISITED, VISITING, VISITED };
+ std::unordered_map<Subclass*, Status> features_status;
+ for (const auto& feature : features) {
+ features_status[feature] = Status::UNVISITED;
+ }
+ std::function<Result<void>(Subclass*)> visit;
+ visit = [&callback, &features_status,
+ &visit](Subclass* feature) -> Result<void> {
+ CF_EXPECT(features_status.count(feature) > 0,
+ "Dependency edge to "
+ << feature->Name() << " but it is not part of the feature "
+ << "graph. This feature is either disabled or not correctly "
+ << "registered.");
+ if (features_status[feature] == Status::VISITED) {
+ return {};
+ }
+ CF_EXPECT(features_status[feature] != Status::VISITING,
+ "Cycle detected while visiting " << feature->Name());
+ features_status[feature] = Status::VISITING;
+ for (const auto& dependency : feature->Dependencies()) {
+ CF_EXPECT(dependency != nullptr,
+ "SetupFeature " << feature->Name() << " has a null dependency.");
+ CF_EXPECT(visit(dependency),
+ "Error detected while visiting " << feature->Name());
+ }
+ features_status[feature] = Status::VISITED;
+ CF_EXPECT(callback(feature), "Callback error on " << feature->Name());
+ return {};
+ };
+ for (const auto& feature : features) {
+ CF_EXPECT(visit(feature)); // `visit` will log the error chain.
+ }
+ return {};
+}
+
+} // namespace cuttlefish
diff --git a/host/libs/config/host_tools_version.cpp b/host/libs/config/host_tools_version.cpp
index 58f0657..17fceb7 100644
--- a/host/libs/config/host_tools_version.cpp
+++ b/host/libs/config/host_tools_version.cpp
@@ -29,7 +29,7 @@
namespace cuttlefish {
-static uint32_t FileCrc(const std::string& path) {
+uint32_t FileCrc(const std::string& path) {
uint32_t crc = crc32(0, (unsigned char*) path.c_str(), path.size());
std::ifstream file_stream(path, std::ifstream::binary);
std::vector<char> data(1024, 0);
diff --git a/host/libs/config/host_tools_version.h b/host/libs/config/host_tools_version.h
index 06b075e..c851692 100644
--- a/host/libs/config/host_tools_version.h
+++ b/host/libs/config/host_tools_version.h
@@ -21,6 +21,7 @@
namespace cuttlefish {
+uint32_t FileCrc(const std::string& path);
std::map<std::string, uint32_t> HostToolsCrc();
} // namespace cuttlefish
diff --git a/host/libs/config/inject.h b/host/libs/config/inject.h
new file mode 100644
index 0000000..4e2a6e0
--- /dev/null
+++ b/host/libs/config/inject.h
@@ -0,0 +1,79 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <fruit/fruit.h>
+#include <type_traits>
+
+namespace cuttlefish {
+
+/**
+ * This is a template helper to add bindings for a set of implementation
+ * classes that may each be part of multiple multibindings. To be more specific,
+ * for these example classes:
+ *
+ * class ImplementationA : public IntX, IntY {};
+ * class ImplementationB : public IntY, IntZ {};
+ *
+ * can be installed with
+ *
+ * using Deps = fruit::Required<...>;
+ * using Bases = Multibindings<Deps>::Bases<IntX, IntY, IntZ>;
+ * return fruit::createComponent()
+ * .install(Bases::Impls<ImplementationA, ImplementationB>);
+ *
+ * Note that not all implementations have to implement all interfaces. Invalid
+ * combinations are filtered out at compile-time through SFINAE.
+ */
+template <typename Deps>
+struct Multibindings {
+ /* SFINAE logic for an individual interface binding. The class does implement
+ * the interface, so add a multibinding. */
+ template <typename Base, typename Impl,
+ std::enable_if_t<std::is_base_of<Base, Impl>::value, bool> = true>
+ static fruit::Component<Deps> OneBaseOneImpl() {
+ return fruit::createComponent().addMultibinding<Base, Impl>();
+ }
+ /* SFINAE logic for an individual interface binding. The class does not
+ * implement the interface, so do not add a multibinding. */
+ template <typename Base, typename Impl,
+ std::enable_if_t<!std::is_base_of<Base, Impl>::value, bool> = true>
+ static fruit::Component<Deps> OneBaseOneImpl() {
+ return fruit::createComponent();
+ }
+
+ template <typename Base>
+ struct OneBase {
+ template <typename... ImplTypes>
+ static fruit::Component<Deps> Impls() {
+ return fruit::createComponent().installComponentFunctions(
+ fruit::componentFunction(OneBaseOneImpl<Base, ImplTypes>)...);
+ }
+ };
+
+ template <typename... BaseTypes>
+ struct Bases {
+ template <typename... ImplTypes>
+ static fruit::Component<Deps> Impls() {
+ return fruit::createComponent().installComponentFunctions(
+ fruit::componentFunction(
+ OneBase<BaseTypes>::template Impls<ImplTypes...>)...);
+ }
+ };
+};
+
+} // namespace cuttlefish
diff --git a/host/libs/config/kernel_args.cpp b/host/libs/config/kernel_args.cpp
index f5393d5..cbfdd5e 100644
--- a/host/libs/config/kernel_args.cpp
+++ b/host/libs/config/kernel_args.cpp
@@ -42,46 +42,34 @@
// substitute for the vm_manager comparisons.
std::vector<std::string> VmManagerKernelCmdline(const CuttlefishConfig& config) {
std::vector<std::string> vm_manager_cmdline;
- if (config.vm_manager() == QemuManager::name() || config.use_bootloader()) {
- // crosvm sets up the console= earlycon= panic= flags for us if booting straight to
- // the kernel, but QEMU and the bootloader via crosvm does not.
- AppendVector(&vm_manager_cmdline, {"console=hvc0", "panic=-1"});
+ if (config.vm_manager() == QemuManager::name()) {
+ vm_manager_cmdline.push_back("console=hvc0");
Arch target_arch = config.target_arch();
if (target_arch == Arch::Arm64 || target_arch == Arch::Arm) {
- if (config.vm_manager() == QemuManager::name()) {
- // To update the pl011 address:
- // $ qemu-system-aarch64 -machine virt -cpu cortex-a57 -machine dumpdtb=virt.dtb
- // $ dtc -O dts -o virt.dts -I dtb virt.dtb
- // In the virt.dts file, look for a uart node
- vm_manager_cmdline.push_back(" earlycon=pl011,mmio32,0x9000000");
- } else {
- // Crosvm ARM only supports earlycon uart over mmio.
- vm_manager_cmdline.push_back(" earlycon=uart8250,mmio,0x3f8");
- }
+ // To update the pl011 address:
+ // $ qemu-system-aarch64 -machine virt -cpu cortex-a57 -machine dumpdtb=virt.dtb
+ // $ dtc -O dts -o virt.dts -I dtb virt.dtb
+ // In the virt.dts file, look for a uart node
+ vm_manager_cmdline.push_back("earlycon=pl011,mmio32,0x9000000");
} else {
// To update the uart8250 address:
// $ qemu-system-x86_64 -kernel bzImage -serial stdio | grep ttyS0
// Only 'io' mode works; mmio and mmio32 do not
vm_manager_cmdline.push_back("earlycon=uart8250,io,0x3f8");
- if (config.vm_manager() == QemuManager::name()) {
- // crosvm doesn't support ACPI PNP, but QEMU does. We need to disable
- // it on QEMU so that the ISA serial ports aren't claimed by ACPI, so
- // we can use serdev with platform devices instead
- vm_manager_cmdline.push_back("pnpacpi=off");
+ // crosvm doesn't support ACPI PNP, but QEMU does. We need to disable
+ // it on QEMU so that the ISA serial ports aren't claimed by ACPI, so
+ // we can use serdev with platform devices instead
+ vm_manager_cmdline.push_back("pnpacpi=off");
- // crosvm sets up the ramoops.xx= flags for us, but QEMU does not.
- // See external/crosvm/x86_64/src/lib.rs
- // this feature is not supported on aarch64
- vm_manager_cmdline.push_back("ramoops.mem_address=0x100000000");
- vm_manager_cmdline.push_back("ramoops.mem_size=0x200000");
- vm_manager_cmdline.push_back("ramoops.console_size=0x80000");
- vm_manager_cmdline.push_back("ramoops.record_size=0x80000");
- vm_manager_cmdline.push_back("ramoops.dump_oops=1");
- } else {
- // crosvm requires these additional parameters on x86_64 in bootloader mode
- AppendVector(&vm_manager_cmdline, {"reboot=k"});
- }
+ // crosvm sets up the ramoops.xx= flags for us, but QEMU does not.
+ // See external/crosvm/x86_64/src/lib.rs
+ // this feature is not supported on aarch64
+ vm_manager_cmdline.push_back("ramoops.mem_address=0x100000000");
+ vm_manager_cmdline.push_back("ramoops.mem_size=0x200000");
+ vm_manager_cmdline.push_back("ramoops.console_size=0x80000");
+ vm_manager_cmdline.push_back("ramoops.record_size=0x80000");
+ vm_manager_cmdline.push_back("ramoops.dump_oops=1");
}
}
@@ -97,23 +85,8 @@
std::vector<std::string> KernelCommandLineFromConfig(
const CuttlefishConfig& config) {
std::vector<std::string> kernel_cmdline;
-
AppendVector(&kernel_cmdline, VmManagerKernelCmdline(config));
-
- if (config.enable_gnss_grpc_proxy()) {
- kernel_cmdline.push_back("gnss_cmdline.serdev=serial8250/serial0/serial0-0");
- kernel_cmdline.push_back("gnss_cmdline.type=0");
- kernel_cmdline.push_back("serdev_ttyport.pdev_tty_port=ttyS1");
- }
-
- if (config.guest_audit_security()) {
- kernel_cmdline.push_back("audit=1");
- } else {
- kernel_cmdline.push_back("audit=0");
- }
-
AppendVector(&kernel_cmdline, config.extra_kernel_cmdline());
-
return kernel_cmdline;
}
diff --git a/guest/hals/audio/Android.bp b/host/libs/config/kernel_log_pipe_provider.h
similarity index 62%
copy from guest/hals/audio/Android.bp
copy to host/libs/config/kernel_log_pipe_provider.h
index c6faae7..c9779ea 100644
--- a/guest/hals/audio/Android.bp
+++ b/host/libs/config/kernel_log_pipe_provider.h
@@ -1,3 +1,4 @@
+//
// Copyright (C) 2019 The Android Open Source Project
//
// Licensed under the Apache License, Version 2.0 (the "License");
@@ -12,16 +13,19 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-package {
- default_applicable_licenses: ["Android-Apache-2.0"],
-}
+#pragma once
-cc_library_shared {
- name: "audio.primary.cutf",
- relative_install_path: "hw",
- defaults: ["cuttlefish_guest_only"],
- vendor: true,
- srcs: ["audio_hw.c"],
- cflags: ["-Wno-unused-parameter"],
- shared_libs: ["libcutils", "libhardware", "liblog", "libtinyalsa"],
-}
+#include <fruit/fruit.h>
+#include <vector>
+
+#include "common/libs/fs/shared_fd.h"
+
+namespace cuttlefish {
+
+class KernelLogPipeProvider : public virtual SetupFeature {
+ public:
+ virtual ~KernelLogPipeProvider() = default;
+ virtual SharedFD KernelLogPipe() = 0;
+};
+
+} // namespace cuttlefish
diff --git a/host/libs/config/known_paths.cpp b/host/libs/config/known_paths.cpp
index 1581a06..f45137e 100644
--- a/host/libs/config/known_paths.cpp
+++ b/host/libs/config/known_paths.cpp
@@ -53,7 +53,7 @@
}
std::string RootCanalBinary() {
- return DefaultHostArtifactsPath("bin/root-canal");
+ return HostBinaryPath("root-canal");
}
std::string SocketVsockProxyBinary() {
@@ -64,6 +64,11 @@
return HostBinaryPath("tombstone_receiver");
}
+std::string VehicleHalGrpcServerBinary() {
+ return HostBinaryPath(
+ "android.hardware.automotive.vehicle@2.0-virtualization-grpc-server");
+}
+
std::string WebRtcBinary() {
return HostBinaryPath("webRTC");
}
@@ -72,8 +77,14 @@
return HostBinaryPath("webrtc_operator");
}
-std::string VncServerBinary() {
- return HostBinaryPath("vnc_server");
+std::string WebRtcSigServerProxyBinary() {
+ return HostBinaryPath("operator_proxy");
+}
+
+std::string WmediumdBinary() { return HostBinaryPath("wmediumd"); }
+
+std::string WmediumdGenConfigBinary() {
+ return HostBinaryPath("wmediumd_gen_config");
}
} // namespace cuttlefish
diff --git a/host/libs/config/known_paths.h b/host/libs/config/known_paths.h
index 71b6253..8c39e61 100644
--- a/host/libs/config/known_paths.h
+++ b/host/libs/config/known_paths.h
@@ -30,8 +30,11 @@
std::string RootCanalBinary();
std::string SocketVsockProxyBinary();
std::string TombstoneReceiverBinary();
+std::string VehicleHalGrpcServerBinary();
std::string WebRtcBinary();
std::string WebRtcSigServerBinary();
-std::string VncServerBinary();
+std::string WebRtcSigServerProxyBinary();
+std::string WmediumdBinary();
+std::string WmediumdGenConfigBinary();
} // namespace cuttlefish
diff --git a/host/libs/config/logging.cpp b/host/libs/config/logging.cpp
index 50a14f8..9cf1705 100644
--- a/host/libs/config/logging.cpp
+++ b/host/libs/config/logging.cpp
@@ -33,10 +33,15 @@
auto instance = config->ForDefaultInstance();
+ std::string prefix = "";
+ if (config->Instances().size() > 1) {
+ prefix = instance.instance_name() + ": ";
+ }
+
if (config->run_as_daemon()) {
SetLogger(LogToFiles({instance.launcher_log_path()}));
} else {
- SetLogger(LogToStderrAndFiles({instance.launcher_log_path()}));
+ SetLogger(LogToStderrAndFiles({instance.launcher_log_path()}, prefix));
}
}
diff --git a/host/libs/confui/Android.bp b/host/libs/confui/Android.bp
index b74eb24..585684e 100644
--- a/host/libs/confui/Android.bp
+++ b/host/libs/confui/Android.bp
@@ -32,18 +32,24 @@
cc_library_static {
name: "libcuttlefish_confui_host",
srcs: [
+ "cbor.cc",
"host_renderer.cc",
"host_server.cc",
"host_utils.cc",
+ "secure_input.cc",
"server_common.cc",
"session.cc",
+ "sign.cc",
"fonts.S",
],
shared_libs: [
+ "libcn-cbor",
"libcuttlefish_fs",
"libbase",
"libjsoncpp",
"liblog",
+ "libcrypto",
+ "[email protected]",
],
header_libs: [
"libcuttlefish_confui_host_headers",
@@ -52,10 +58,11 @@
"libcuttlefish_host_config",
"libcuttlefish_utils",
"libcuttlefish_confui",
+ "libcuttlefish_security",
"libcuttlefish_wayland_server",
"libft2.nodep",
"libteeui",
"libteeui_localization",
],
- defaults: ["cuttlefish_host"],
+ defaults: ["cuttlefish_buildhost_only"],
}
diff --git a/host/libs/confui/cbor.cc b/host/libs/confui/cbor.cc
new file mode 100644
index 0000000..e7f667b
--- /dev/null
+++ b/host/libs/confui/cbor.cc
@@ -0,0 +1,108 @@
+/*
+ * Copyright 2021, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "host/libs/confui/cbor.h"
+
+#include "common/libs/confui/confui.h"
+
+namespace cuttlefish {
+namespace confui {
+/**
+ * basically, this creates a map as follows:
+ * {"prompt" : prompt_text_in_UTF8,
+ * "extra" : extra_data_in_bytes}
+ */
+void Cbor::Init() {
+ cn_cbor_errback err;
+ cb_map_ = std::unique_ptr<cn_cbor, CborDeleter>(cn_cbor_map_create(&err));
+
+ buffer_status_ = CheckUTF8Copy(prompt_text_);
+ if (!IsOk()) {
+ return;
+ }
+
+ auto cb_prompt_as_value = cn_cbor_string_create(prompt_text_.data(), &err);
+ auto cb_extra_data_as_value =
+ cn_cbor_data_create(extra_data_.data(), extra_data_.size(), &err);
+ cn_cbor_mapput_string(cb_map_.get(), "prompt", cb_prompt_as_value, &err);
+ cn_cbor_mapput_string(cb_map_.get(), "extra", cb_extra_data_as_value, &err);
+
+ // cn_cbor_encoder_write wants buffer_ to have a trailing 0 at the end
+ auto n_chars =
+ cn_cbor_encoder_write(buffer_.data(), 0, buffer_.size(), cb_map_.get());
+ ConfUiLog(ERROR) << "Cn-cbor encoder wrote " << n_chars << " while "
+ << "kMax is " << kMax;
+ if (n_chars < 0) {
+ // it's either message being too long, or a potential cn_cbor bug
+ ConfUiLog(ERROR) << "Cn-cbor returns -1 which is likely message too long.";
+ buffer_status_ = Error::OUT_OF_DATA;
+ }
+ if (!IsOk()) {
+ return;
+ }
+ buffer_.resize(n_chars);
+}
+
+std::vector<std::uint8_t>&& Cbor::GetMessage() { return std::move(buffer_); }
+
+Cbor::Error Cbor::CheckUTF8Copy(const std::string& text) {
+ auto begin = text.cbegin();
+ auto end = text.cend();
+
+ if (!IsOk()) {
+ return buffer_status_;
+ }
+
+ uint32_t multi_byte_length = 0;
+ Cbor::Error err_code = buffer_status_; // OK
+
+ while (begin != end) {
+ if (multi_byte_length) {
+ // parsing multi byte character - must start with 10xxxxxx
+ --multi_byte_length;
+ if ((*begin & 0xc0) != 0x80) {
+ return Cbor::Error::MALFORMED_UTF8;
+ }
+ } else if (!((*begin) & 0x80)) {
+ // 7bit character -> nothing to be done
+ } else {
+ // msb is set and we were not parsing a multi byte character
+ // so this must be a header byte
+ char c = *begin << 1;
+ while (c & 0x80) {
+ ++multi_byte_length;
+ c <<= 1;
+ }
+ // headers of the form 10xxxxxx are not allowed
+ if (multi_byte_length < 1) {
+ return Cbor::Error::MALFORMED_UTF8;
+ }
+ // chars longer than 4 bytes are not allowed (multi_byte_length does not
+ // count the header thus > 3
+ if (multi_byte_length > 3) {
+ return Cbor::Error::MALFORMED_UTF8;
+ }
+ }
+ ++begin;
+ }
+ // if the string ends in the middle of a multi byte char it is invalid
+ if (multi_byte_length) {
+ return Cbor::Error::MALFORMED_UTF8;
+ }
+ return err_code;
+}
+} // end of namespace confui
+} // end of namespace cuttlefish
diff --git a/host/libs/confui/cbor.h b/host/libs/confui/cbor.h
new file mode 100644
index 0000000..b77e0c7
--- /dev/null
+++ b/host/libs/confui/cbor.h
@@ -0,0 +1,101 @@
+/*
+ * Copyright 2021, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <cstdint>
+#include <memory>
+#include <string>
+#include <vector>
+
+#include <android/hardware/keymaster/4.0/types.h>
+
+#include <cn-cbor/cn-cbor.h>
+
+namespace cuttlefish {
+namespace confui {
+
+/** take prompt_text_, extra_data
+ * returns CBOR map, created with the two
+ *
+ * Usage:
+ * if (IsOk()) GetMessage()
+ *
+ * The CBOR map is used to create signed confirmation
+ */
+class Cbor {
+ enum class Error : uint32_t {
+ OK = 0,
+ OUT_OF_DATA = 1,
+ MALFORMED = 2,
+ MALFORMED_UTF8 = 3,
+ };
+
+ enum class MessageSize : uint32_t { MAX = 6144u };
+
+ enum class Type : uint8_t {
+ NUMBER = 0,
+ NEGATIVE = 1,
+ BYTE_STRING = 2,
+ TEXT_STRING = 3,
+ ARRAY = 4,
+ MAP = 5,
+ TAG = 6,
+ FLOAT = 7,
+ };
+
+ public:
+ Cbor(const std::string& prompt_text,
+ const std::vector<std::uint8_t>& extra_data)
+ : prompt_text_(prompt_text),
+ extra_data_(extra_data),
+ buffer_status_{Error::OK},
+ buffer_(kMax + 1) {
+ Init();
+ }
+
+ bool IsOk() const { return buffer_status_ == Error::OK; }
+ Error GetErrorCode() const { return buffer_status_; }
+ bool IsMessageTooLong() const { return buffer_status_ == Error::OUT_OF_DATA; }
+ bool IsMalformedUtf8() const {
+ return buffer_status_ == Error::MALFORMED_UTF8;
+ }
+ // call this only when IsOk() returns true
+ std::vector<std::uint8_t>&& GetMessage();
+
+ /** When encoded, the Cbor object should not exceed this limit in terms of
+ * size in bytes
+ */
+ const std::uint32_t kMax = static_cast<std::uint32_t>(MessageSize::MAX);
+
+ private:
+ class CborDeleter {
+ public:
+ void operator()(cn_cbor* ptr) { cn_cbor_free(ptr); }
+ };
+
+ std::unique_ptr<cn_cbor, CborDeleter> cb_map_;
+ std::string prompt_text_;
+ std::vector<std::uint8_t> extra_data_;
+ Error buffer_status_;
+ std::vector<std::uint8_t> buffer_;
+
+ void Init();
+ Error CheckUTF8Copy(const std::string& text);
+};
+
+} // namespace confui
+} // end of namespace cuttlefish
diff --git a/host/libs/confui/host_mode_ctrl.h b/host/libs/confui/host_mode_ctrl.h
index 7cf991c..bf8b575 100644
--- a/host/libs/confui/host_mode_ctrl.h
+++ b/host/libs/confui/host_mode_ctrl.h
@@ -30,7 +30,7 @@
* mechanism to orchestrate concurrent executions of threads
* that work for screen connector
*
- * Within VNC or WebRTC service, it tells when it is now in the Android Mode or
+ * Within WebRTC service, it tells when it is now in the Android Mode or
* Confirmation UI mode
*/
class HostModeCtrl {
@@ -76,14 +76,14 @@
void SetMode(const ModeType mode) {
ConfUiLog(DEBUG) << cuttlefish::confui::thread::GetName()
- << "tries to acquire the lock in SetMode";
+ << " tries to acquire the lock in SetMode";
std::lock_guard<std::mutex> lock(mode_mtx_);
ConfUiLog(DEBUG) << cuttlefish::confui::thread::GetName()
- << "acquired the lock in SetMode";
+ << " acquired the lock in SetMode";
atomic_mode_ = mode;
if (atomic_mode_ == ModeType::kAndroidMode) {
ConfUiLog(DEBUG) << cuttlefish::confui::thread::GetName()
- << "signals kAndroidMode in SetMode";
+ << " signals kAndroidMode in SetMode";
and_mode_cv_.notify_all();
return;
}
diff --git a/host/libs/confui/host_renderer.cc b/host/libs/confui/host_renderer.cc
index f3e0c52..8bf218b 100644
--- a/host/libs/confui/host_renderer.cc
+++ b/host/libs/confui/host_renderer.cc
@@ -16,6 +16,8 @@
#include "host/libs/confui/host_renderer.h"
+#include "host/libs/config/cuttlefish_config.h"
+
namespace cuttlefish {
namespace confui {
static teeui::Color alfaCombineChannel(std::uint32_t shift, double alfa,
@@ -31,31 +33,70 @@
return result << shift;
}
-ConfUiRenderer::ConfUiRenderer(const std::uint32_t display)
- : display_num_(display),
- lang_id_{"en"},
- prompt_("Am I Yumi Meow?"),
- current_height_(ScreenConnectorInfo::ScreenHeight(display_num_)),
- current_width_(ScreenConnectorInfo::ScreenWidth(display_num_)),
- color_bg_{kColorBackground},
- color_text_{kColorDisabled},
- shield_color_{kColorShield},
- is_inverted_{false},
- ctx_{GetDeviceContext()} {
- auto opted_frame = RepaintRawFrame(prompt_, lang_id_);
- if (opted_frame) {
- raw_frame_ = std::move(opted_frame.value());
+std::unique_ptr<ConfUiRenderer> ConfUiRenderer::GenerateRenderer(
+ const std::uint32_t display, const std::string& confirmation_msg,
+ const std::string& locale, const bool inverted, const bool magnified) {
+ ConfUiRenderer* raw_ptr = new ConfUiRenderer(display, confirmation_msg,
+ locale, inverted, magnified);
+ if (raw_ptr && raw_ptr->IsSetUpSuccessful()) {
+ return std::unique_ptr<ConfUiRenderer>(raw_ptr);
}
+ return nullptr;
}
-void ConfUiRenderer::SetConfUiMessage(const std::string& msg) {
- prompt_ = msg;
- SetText<LabelConfMsg>(msg);
+static int GetDpi(const int display_num = 0) {
+ auto config = CuttlefishConfig::Get();
+ CHECK(config) << "Config is Missing";
+ auto display_configs = config->display_configs();
+ CHECK_GT(display_configs.size(), display_num)
+ << "Invalid display number " << display_num;
+ return display_configs[display_num].dpi;
}
-teeui::Error ConfUiRenderer::SetLangId(const std::string& lang_id) {
+/**
+ * device configuration
+ *
+ * ctx_{# of pixels in 1 mm, # of pixels per 1 density independent pixels}
+ *
+ * The numbers are, however, to fit for the host webRTC local/remote clients
+ * in general, not necessarily the supposedly guest device (e.g. Auto, phone,
+ * etc)
+ *
+ * In general, for a normal PC, roughly ctx_(6.45211, 400.0/412.0) is a good
+ * combination for the default DPI, 320. If we want to see the impact
+ * of the change in the guest DPI, we could adjust the combination above
+ * proportionally
+ *
+ */
+ConfUiRenderer::ConfUiRenderer(const std::uint32_t display,
+ const std::string& confirmation_msg,
+ const std::string& locale, const bool inverted,
+ const bool magnified)
+ : display_num_{display},
+ lang_id_{locale},
+ prompt_text_{confirmation_msg},
+ current_height_{ScreenConnectorInfo::ScreenHeight(display_num_)},
+ current_width_{ScreenConnectorInfo::ScreenWidth(display_num_)},
+ is_inverted_(inverted),
+ is_magnified_(magnified),
+ ctx_(6.45211 * GetDpi() / 320.0, 400.0 / 412.0 * GetDpi() / 320.0),
+ is_setup_well_(false) {
+ SetDeviceContext(current_width_, current_height_, is_inverted_,
+ is_magnified_);
+ layout_ = teeui::instantiateLayout(teeui::ConfUILayout(), ctx_);
+
+ if (auto error = UpdateLocale()) {
+ ConfUiLog(ERROR) << "Update Translation Error: " << Enum2Base(error.code());
+ // is_setup_well_ = false;
+ return;
+ }
+ UpdateColorScheme(is_inverted_);
+ SetText<LabelConfMsg>(prompt_text_);
+ is_setup_well_ = true;
+}
+
+teeui::Error ConfUiRenderer::UpdateLocale() {
using teeui::Error;
- lang_id_ = lang_id;
teeui::localization::selectLangId(lang_id_.c_str());
if (auto error = UpdateTranslations()) {
return error;
@@ -63,14 +104,25 @@
return Error::OK;
}
+template <typename Label>
+teeui::Error ConfUiRenderer::UpdateString() {
+ using namespace teeui;
+ const char* str;
+ auto& label = std::get<Label>(layout_);
+ str = localization::lookup(TranslationId(label.textId()));
+ if (str == nullptr) {
+ ConfUiLog(ERROR) << "Given translation_id" << label.textId() << "not found";
+ return Error::Localization;
+ }
+ label.setText({str, str + strlen(str)});
+ return Error::OK;
+}
+
teeui::Error ConfUiRenderer::UpdateTranslations() {
using namespace teeui;
if (auto error = UpdateString<LabelOK>()) {
return error;
}
- if (auto error = UpdateString<LabelOK>()) {
- return error;
- }
if (auto error = UpdateString<LabelCancel>()) {
return error;
}
@@ -83,41 +135,41 @@
return Error::OK;
}
-teeui::context<teeui::ConUIParameters> ConfUiRenderer::GetDeviceContext() {
+void ConfUiRenderer::SetDeviceContext(const unsigned long long w,
+ const unsigned long long h,
+ const bool is_inverted,
+ const bool is_magnified) {
using namespace teeui;
- const unsigned long long w = ScreenConnectorInfo::ScreenWidth(display_num_);
- const unsigned long long h = ScreenConnectorInfo::ScreenHeight(display_num_);
const auto screen_width = operator""_px(w);
const auto screen_height = operator""_px(h);
- context<teeui::ConUIParameters> ctx(6.45211, 400.0 / 412.0);
- ctx.setParam<RightEdgeOfScreen>(screen_width);
- ctx.setParam<BottomOfScreen>(screen_height);
- ctx.setParam<PowerButtonTop>(20.26_mm);
- ctx.setParam<PowerButtonBottom>(30.26_mm);
- ctx.setParam<VolUpButtonTop>(40.26_mm);
- ctx.setParam<VolUpButtonBottom>(50.26_mm);
- ctx.setParam<DefaultFontSize>(14_dp);
- ctx.setParam<BodyFontSize>(16_dp);
- return ctx;
-}
-
-bool ConfUiRenderer::InitLayout(const std::string& lang_id) {
- layout_ = teeui::instantiateLayout(teeui::ConfUILayout(), ctx_);
- SetLangId(lang_id);
- if (auto error = UpdateTranslations()) {
- ConfUiLog(ERROR) << "Update Translation Error";
- return false;
+ ctx_.setParam<RightEdgeOfScreen>(pxs(screen_width));
+ ctx_.setParam<BottomOfScreen>(pxs(screen_height));
+ if (is_magnified) {
+ ctx_.setParam<DefaultFontSize>(18_dp);
+ ctx_.setParam<BodyFontSize>(20_dp);
+ } else {
+ ctx_.setParam<DefaultFontSize>(14_dp);
+ ctx_.setParam<BodyFontSize>(16_dp);
}
- UpdateColorScheme(&ctx_);
- return true;
+ if (is_inverted) {
+ ctx_.setParam<ShieldColor>(kColorShieldInv);
+ ctx_.setParam<ColorText>(kColorTextInv);
+ ctx_.setParam<ColorBG>(kColorBackgroundInv);
+ ctx_.setParam<ColorButton>(kColorShieldInv);
+ } else {
+ ctx_.setParam<ShieldColor>(kColorShield);
+ ctx_.setParam<ColorText>(kColorText);
+ ctx_.setParam<ColorBG>(kColorBackground);
+ ctx_.setParam<ColorButton>(kColorShield);
+ }
}
-teeui::Error ConfUiRenderer::UpdatePixels(TeeUiFrame& raw_frame,
+teeui::Error ConfUiRenderer::UpdatePixels(TeeUiFrameWrapper& raw_frame,
std::uint32_t x, std::uint32_t y,
teeui::Color color) {
auto buffer = raw_frame.data();
- const auto height = ScreenConnectorInfo::ScreenHeight(display_num_);
- const auto width = ScreenConnectorInfo::ScreenWidth(display_num_);
+ const auto height = raw_frame.Height();
+ const auto width = raw_frame.Width();
auto pos = width * y + x;
if (pos >= (height * width)) {
ConfUiLog(ERROR) << "Rendering Out of Bound";
@@ -131,114 +183,69 @@
return teeui::Error::OK;
}
-std::tuple<TeeUiFrame&, bool> ConfUiRenderer::RenderRawFrame(
- const std::string& confirmation_msg, const std::string& lang_id) {
+void ConfUiRenderer::UpdateColorScheme(const bool is_inverted) {
+ using namespace teeui;
+ color_text_ = is_inverted ? kColorDisabledInv : kColorDisabled;
+ shield_color_ = is_inverted ? kColorShieldInv : kColorShield;
+ color_bg_ = is_inverted ? kColorBackgroundInv : kColorBackground;
+
+ ctx_.setParam<ShieldColor>(shield_color_);
+ ctx_.setParam<ColorText>(color_text_);
+ ctx_.setParam<ColorBG>(color_bg_);
+ return;
+}
+
+std::shared_ptr<TeeUiFrameWrapper> ConfUiRenderer::RenderRawFrame() {
/* we repaint only if one or more of the followng meet:
*
* 1. raw_frame_ is empty
* 2. the current_width_ and current_height_ is out of date
- * 3. lang_id is different (e.g. new locale)
*
- * in the future, maybe you wanna inverted, new background, etc?
*/
- if (lang_id != lang_id_ || !IsFrameReady() ||
- current_height_ != ScreenConnectorInfo::ScreenHeight(display_num_) ||
- current_width_ != ScreenConnectorInfo::ScreenWidth(display_num_)) {
- auto opted_new_frame = RepaintRawFrame(confirmation_msg, lang_id_);
- if (opted_new_frame) {
- // repainting from the scratch successful in a new frame
- raw_frame_ = std::move(opted_new_frame.value());
- return {raw_frame_, true};
+ const int w = ScreenConnectorInfo::ScreenWidth(display_num_);
+ const int h = ScreenConnectorInfo::ScreenHeight(display_num_);
+ if (!IsFrameReady() || current_height_ != h || current_width_ != w) {
+ auto new_frame = RepaintRawFrame(w, h);
+ if (!new_frame) {
+ // must repaint but failed
+ raw_frame_ = nullptr;
+ return nullptr;
}
- // repaint failed even if it was necessary, so returns invalid values
- raw_frame_.clear();
- return {raw_frame_, false};
+ // repainting from the scratch successful in a new frame
+ raw_frame_ = std::move(new_frame);
+ current_width_ = w;
+ current_height_ = h;
}
- // no need to repaint from the scratch. repaint the confirmation message only
- // the frame is mostly already in raw_frame_
- auto ret_code = RenderConfirmationMsgOnly(confirmation_msg);
- return {raw_frame_, (ret_code == teeui::Error::OK)};
+ return raw_frame_;
}
-std::optional<TeeUiFrame> ConfUiRenderer::RepaintRawFrame(
- const std::string& confirmation_msg, const std::string& lang_id) {
- /*
- * NOTE: DON'T use current_width_/height_ to create this frame
- * it may fail, and then we must not mess up the current_width_, height_
- *
- */
- if (!InitLayout(lang_id)) {
- return std::nullopt;
- }
- SetConfUiMessage(confirmation_msg);
- auto color = kColorEnabled;
- std::get<teeui::LabelOK>(layout_).setTextColor(color);
- std::get<teeui::LabelCancel>(layout_).setTextColor(color);
+std::unique_ptr<TeeUiFrameWrapper> ConfUiRenderer::RepaintRawFrame(
+ const int w, const int h) {
+ std::get<teeui::LabelOK>(layout_).setTextColor(kColorEnabled);
+ std::get<teeui::LabelCancel>(layout_).setTextColor(kColorEnabled);
- /* in the future, if ever we need to register a handler for the
- Label{OK,Cancel}. do this: std::get<teeui::LabelOK>(layout_)
- .setCB(teeui::makeCallback<teeui::Error, teeui::Event>(
- [](teeui::Event e, void* p) -> teeui::Error {
- LOG(DEBUG) << "Calling callback for Confirm?";
- return reinterpret_cast<decltype(owner)*>(p)->TapOk(e); },
- owner));
- */
- // we manually check if click happened, where if yes, and generate the label
- // event manually. So we won't register the handler here.
/**
* should be uint32_t for teeui APIs.
* It assumes that each raw frame buffer element is 4 bytes
*/
- TeeUiFrame new_raw_frame(
- ScreenConnectorInfo::ScreenSizeInBytes(display_num_) / 4,
- kColorBackground);
-
+ const teeui::Color background_color =
+ is_inverted_ ? kColorBackgroundInv : kColorBackground;
+ auto new_raw_frame =
+ std::make_unique<TeeUiFrameWrapper>(w, h, background_color);
auto draw_pixel = teeui::makePixelDrawer(
[this, &new_raw_frame](std::uint32_t x, std::uint32_t y,
teeui::Color color) -> teeui::Error {
- return this->UpdatePixels(new_raw_frame, x, y, color);
+ return this->UpdatePixels(*new_raw_frame, x, y, color);
});
// render all components
const auto error = drawElements(layout_, draw_pixel);
if (error) {
ConfUiLog(ERROR) << "Painting failed: " << error.code();
- return std::nullopt;
+ return nullptr;
}
- // set current frame's dimension as frame generation was successful
- current_height_ = ScreenConnectorInfo::ScreenHeight(display_num_);
- current_width_ = ScreenConnectorInfo::ScreenWidth(display_num_);
-
- return {new_raw_frame};
-}
-
-teeui::Error ConfUiRenderer::RenderConfirmationMsgOnly(
- const std::string& confirmation_msg) {
- // repaint labelbody on the raw_frame__ only
- auto callback_func = [this](std::uint32_t x, std::uint32_t y,
- teeui::Color color) -> teeui::Error {
- return UpdatePixels(raw_frame_, x, y, color);
- };
- auto draw_pixel = teeui::makePixelDrawer(callback_func);
- LabelConfMsg& label = std::get<LabelConfMsg>(layout_);
- auto b = GetBoundary(label);
- for (std::uint32_t i = 0; i != b.w; i++) {
- const auto col_index = i + b.x - 1;
- for (std::uint32_t j = 0; j != b.y; j++) {
- const auto row_index = (j + b.y - 1);
- raw_frame_[current_width_ * row_index + col_index] = color_bg_;
- }
- }
-
- SetConfUiMessage(confirmation_msg);
- ConfUiLog(DEBUG) << "Repaint Confirmation Msg with :" << prompt_;
- if (auto error = std::get<LabelConfMsg>(layout_).draw(draw_pixel)) {
- ConfUiLog(ERROR) << "Repainting Confirmation Message Label failed:"
- << error.code();
- return error;
- }
- return teeui::Error::OK;
+ return new_raw_frame;
}
} // end of namespace confui
diff --git a/host/libs/confui/host_renderer.h b/host/libs/confui/host_renderer.h
index c614109..6dac3e3 100644
--- a/host/libs/confui/host_renderer.h
+++ b/host/libs/confui/host_renderer.h
@@ -29,45 +29,78 @@
#include "common/libs/confui/confui.h"
#include "host/libs/confui/layouts/layout.h"
#include "host/libs/confui/server_common.h"
-#include "host/libs/screen_connector/screen_connector.h"
+#include "host/libs/screen_connector/screen_connector_common.h"
namespace cuttlefish {
namespace confui {
+class TeeUiFrameWrapper {
+ public:
+ TeeUiFrameWrapper(const int w, const int h, const teeui::Color color)
+ : w_(w), h_(h), teeui_frame_(ScreenSizeInBytes(w, h), color) {}
+ TeeUiFrameWrapper() = delete;
+ auto data() { return teeui_frame_.data(); }
+ int Width() const { return w_; }
+ int Height() const { return h_; }
+ bool IsEmpty() const { return teeui_frame_.empty(); }
+ auto Size() const { return teeui_frame_.size(); }
+ auto& operator[](const int idx) { return teeui_frame_[idx]; }
+ std::uint32_t ScreenStrideBytes() const {
+ return ScreenConnectorInfo::ComputeScreenStrideBytes(w_);
+ }
+
+ private:
+ static std::uint32_t ScreenSizeInBytes(const int w, const int h) {
+ return ScreenConnectorInfo::ComputeScreenSizeInBytes(w, h);
+ }
+
+ int w_;
+ int h_;
+ TeeUiFrame teeui_frame_;
+};
/**
* create a raw frame for confirmation UI dialog
+ *
+ * Many rendering code borrowed from the following source
+ * https://android.googlesource.com/trusty/app/confirmationui/+/0429cc7/src
*/
class ConfUiRenderer {
public:
using LabelConfMsg = teeui::LabelBody;
- ConfUiRenderer(const std::uint32_t display);
+ static std::unique_ptr<ConfUiRenderer> GenerateRenderer(
+ const std::uint32_t display, const std::string& confirmation_msg,
+ const std::string& locale, const bool inverted, const bool magnified);
/**
* this does not repaint from the scratch all the time
*
- * Unless repainting the whole thing is needed, it remove the message
- * label, and re-draw there. There seems yet no fancy way of doing this.
- * Thus, it repaint the background color on the top of the label, and
- * draw the label on the new background
- *
- * As HostRenderer is intended to be shared across sessions, HostRender
- * owns the buffer, and returns reference to the buffer. Note that no
- * 2 or more sessions are concurrently executed. Only 1 or 0 is active
- * at the given moment.
+ * It does repaint its frame buffer only when w/h of
+ * current display has changed
*/
- std::tuple<TeeUiFrame&, bool> RenderRawFrame(
- const std::string& confirmation_msg, const std::string& lang_id = "en");
+ std::shared_ptr<TeeUiFrameWrapper> RenderRawFrame();
- bool IsFrameReady() const { return !raw_frame_.empty(); }
+ bool IsFrameReady() const { return raw_frame_ && !raw_frame_->IsEmpty(); }
+
+ bool IsInConfirm(const std::uint32_t x, const std::uint32_t y) {
+ return IsInside<teeui::LabelOK>(x, y);
+ }
+ bool IsInCancel(const std::uint32_t x, const std::uint32_t y) {
+ return IsInside<teeui::LabelCancel>(x, y);
+ }
private:
+ bool IsSetUpSuccessful() const { return is_setup_well_; }
+ ConfUiRenderer(const std::uint32_t display,
+ const std::string& confirmation_msg, const std::string& locale,
+ const bool inverted, const bool magnified);
+
struct Boundary { // inclusive but.. LayoutElement's size is float
std::uint32_t x, y, w, h; // (x, y) is the top left
};
template <typename LayoutElement>
- Boundary GetBoundary(LayoutElement&& e) {
+ Boundary GetBoundary(LayoutElement&& e) const {
auto box = e.bounds_;
Boundary b;
// (x,y) is left top. so floor() makes sense
@@ -80,28 +113,28 @@
return b;
}
+ template <typename Element>
+ bool IsInside(const std::uint32_t x, const std::uint32_t y) const {
+ auto box = GetBoundary(std::get<Element>(layout_));
+ if (x >= box.x && x <= box.x + box.w && y >= box.y && y <= box.y + box.h) {
+ return true;
+ }
+ return false;
+ }
// essentially, to repaint from the scratch, so returns new frame
// when successful. Or, nullopt
- std::optional<TeeUiFrame> RepaintRawFrame(const std::string& confirmation_msg,
- const std::string& lang_id = "en");
+ std::unique_ptr<TeeUiFrameWrapper> RepaintRawFrame(const int w, const int h);
bool InitLayout(const std::string& lang_id);
teeui::Error UpdateTranslations();
- /**
- * could be confusing. update prompt_, and update the text_ in the Label
- * object, the GUI components. This does not render immediately. And..
- * to render it, we must clean up the existing dirty pixels, which
- * this method does not do.
- */
- void SetConfUiMessage(const std::string& s);
- teeui::Error SetLangId(const std::string& lang_id);
- teeui::context<teeui::ConUIParameters> GetDeviceContext();
+ teeui::Error UpdateLocale();
+ void SetDeviceContext(const unsigned long long w, const unsigned long long h,
+ bool is_inverted, bool is_magnified);
- // effectively, will be send to teeui as a callback function
- teeui::Error UpdatePixels(TeeUiFrame& buffer, std::uint32_t x,
+ // a callback function to be effectively sent to TeeUI library
+ teeui::Error UpdatePixels(TeeUiFrameWrapper& buffer, std::uint32_t x,
std::uint32_t y, teeui::Color color);
- // from Trusty
// second param is for type deduction
template <typename... Elements>
static teeui::Error drawElements(std::tuple<Elements...>& layout,
@@ -111,70 +144,49 @@
// draw the remaining elements in the order they appear in the layout tuple.
return (std::get<Elements>(layout).draw(drawPixel) || ...);
}
-
- // repaint the confirmation UI label only
- teeui::Error RenderConfirmationMsgOnly(const std::string& confirmation_msg);
-
- // from Trusty
- template <typename Context>
- void UpdateColorScheme(Context* ctx) {
- using namespace teeui;
- color_text_ = is_inverted_ ? kColorDisabledInv : kColorDisabled;
- shield_color_ = is_inverted_ ? kColorShieldInv : kColorShield;
- color_bg_ = is_inverted_ ? kColorBackgroundInv : kColorBackground;
-
- ctx->template setParam<ShieldColor>(shield_color_);
- ctx->template setParam<ColorText>(color_text_);
- ctx->template setParam<ColorBG>(color_bg_);
- return;
- }
-
+ void UpdateColorScheme(const bool is_inverted);
template <typename Label>
auto SetText(const std::string& text) {
return std::get<Label>(layout_).setText(
{text.c_str(), text.c_str() + text.size()});
}
- /**
- * source:
- * https://android.googlesource.com/trusty/app/confirmationui/+/0429cc7/src/trusty_confirmation_ui.cpp#49
- */
template <typename Label>
- teeui::Error UpdateString() {
- using namespace teeui;
- const char* str;
- auto& label = std::get<Label>(layout_);
- str = localization::lookup(TranslationId(label.textId()));
- if (str == nullptr) {
- ConfUiLog(ERROR) << "Given translation_id" << label.textId()
- << "not found";
- return Error::Localization;
- }
- label.setText({str, str + strlen(str)});
- return Error::OK;
- }
+ teeui::Error UpdateString();
- const int display_num_;
+ std::uint32_t display_num_;
teeui::layout_t<teeui::ConfUILayout> layout_;
std::string lang_id_;
- std::string prompt_; // confirmation ui message
- TeeUiFrame raw_frame_;
+ std::string prompt_text_; // confirmation ui message
+
+ /**
+ * Potentially, the same frame could be requested multiple times.
+ *
+ * While another thread/caller is using this frame, the frame should
+ * be kept here, too, to be returned upon future requests.
+ *
+ */
+ std::shared_ptr<TeeUiFrameWrapper> raw_frame_;
std::uint32_t current_height_;
std::uint32_t current_width_;
teeui::Color color_bg_;
teeui::Color color_text_;
teeui::Color shield_color_;
bool is_inverted_;
- teeui::context<teeui::ConUIParameters> ctx_;
+ bool is_magnified_;
+ teeui::context<teeui::ConfUIParameters> ctx_;
+ bool is_setup_well_;
- static constexpr const teeui::Color kColorEnabled = 0xff212121;
- static constexpr const teeui::Color kColorDisabled = 0xffbdbdbd;
- static constexpr const teeui::Color kColorEnabledInv = 0xffdedede;
- static constexpr const teeui::Color kColorDisabledInv = 0xff424242;
static constexpr const teeui::Color kColorBackground = 0xffffffff;
static constexpr const teeui::Color kColorBackgroundInv = 0xff212121;
- static constexpr const teeui::Color kColorShieldInv = 0xffc4cb80;
+ static constexpr const teeui::Color kColorDisabled = 0xffbdbdbd;
+ static constexpr const teeui::Color kColorDisabledInv = 0xff424242;
+ static constexpr const teeui::Color kColorEnabled = 0xff212121;
+ static constexpr const teeui::Color kColorEnabledInv = 0xffdedede;
static constexpr const teeui::Color kColorShield = 0xff778500;
+ static constexpr const teeui::Color kColorShieldInv = 0xffc4cb80;
+ static constexpr const teeui::Color kColorText = 0xff212121;
+ static constexpr const teeui::Color kColorTextInv = 0xffdedede;
};
} // end of namespace confui
} // end of namespace cuttlefish
diff --git a/host/libs/confui/host_server.cc b/host/libs/confui/host_server.cc
index c14e0ed..b510759 100644
--- a/host/libs/confui/host_server.cc
+++ b/host/libs/confui/host_server.cc
@@ -22,8 +22,10 @@
#include <tuple>
#include "common/libs/confui/confui.h"
+#include "common/libs/fs/shared_buf.h"
#include "host/libs/config/cuttlefish_config.h"
#include "host/libs/confui/host_utils.h"
+#include "host/libs/confui/secure_input.h"
namespace cuttlefish {
namespace confui {
@@ -33,8 +35,31 @@
return config->ForDefaultInstance();
}
-static std::string HalGuestSocketPath() {
- return CuttlefishConfigDefaultInstance().confui_hal_guest_socket_path();
+static int HalHostVsockPort() {
+ return CuttlefishConfigDefaultInstance().confui_host_vsock_port();
+}
+
+/**
+ * null if not user/touch, or wrap it and ConfUiSecure{Selection,Touch}Message
+ *
+ * ConfUiMessage must NOT ConfUiSecure{Selection,Touch}Message types
+ */
+static std::unique_ptr<ConfUiMessage> WrapWithSecureFlag(
+ const ConfUiMessage& base_msg, const bool secure) {
+ switch (base_msg.GetType()) {
+ case ConfUiCmd::kUserInputEvent: {
+ const ConfUiUserSelectionMessage& as_selection =
+ static_cast<const ConfUiUserSelectionMessage&>(base_msg);
+ return ToSecureSelectionMessage(as_selection, secure);
+ }
+ case ConfUiCmd::kUserTouchEvent: {
+ const ConfUiUserTouchMessage& as_touch =
+ static_cast<const ConfUiUserTouchMessage&>(base_msg);
+ return ToSecureTouchMessage(as_touch, secure);
+ }
+ default:
+ return nullptr;
+ }
}
HostServer& HostServer::Get(
@@ -50,16 +75,24 @@
: display_num_(0),
host_mode_ctrl_(host_mode_ctrl),
screen_connector_{screen_connector},
- renderer_(display_num_),
- hal_socket_path_(HalGuestSocketPath()),
- input_multiplexer_{/* max n_elems */ 20, /* n_Qs */ 2} {
- hal_cmd_q_id_ = input_multiplexer_.GetNewQueueId(); // return 0
- user_input_evt_q_id_ = input_multiplexer_.GetNewQueueId(); // return 1
+ hal_vsock_port_(HalHostVsockPort()) {
+ ConfUiLog(DEBUG) << "Confirmation UI Host session is listening on: "
+ << hal_vsock_port_;
+ const size_t max_elements = 20;
+ auto ignore_new =
+ [](ThreadSafeQueue<std::unique_ptr<ConfUiMessage>>::QueueImpl*) {
+ // no op, so the queue is still full, and the new item will be discarded
+ return;
+ };
+ hal_cmd_q_id_ = input_multiplexer_.RegisterQueue(
+ HostServer::Multiplexer::CreateQueue(max_elements, ignore_new));
+ user_input_evt_q_id_ = input_multiplexer_.RegisterQueue(
+ HostServer::Multiplexer::CreateQueue(max_elements, ignore_new));
}
void HostServer::Start() {
- guest_hal_socket_ = cuttlefish::SharedFD::SocketLocalServer(
- hal_socket_path_, false, SOCK_STREAM, 0666);
+ guest_hal_socket_ =
+ cuttlefish::SharedFD::VsockServer(hal_vsock_port_, SOCK_STREAM);
if (!guest_hal_socket_->IsOpen()) {
ConfUiLog(FATAL) << "Confirmation UI host service mandates a server socket"
<< "to which the guest HAL to connect.";
@@ -70,67 +103,74 @@
hal_input_fetcher_thread_ =
thread::RunThread("HalInputLoop", hal_cmd_fetching);
main_loop_thread_ = thread::RunThread("MainLoop", main);
- ConfUiLog(DEBUG) << "configured internal socket based input.";
+ ConfUiLog(DEBUG) << "configured internal vsock based input.";
return;
}
void HostServer::HalCmdFetcherLoop() {
- hal_cli_socket_ = EstablishHalConnection();
- if (!hal_cli_socket_->IsOpen()) {
- ConfUiLog(FATAL)
- << "Confirmation UI host service mandates connection with HAL.";
- return;
- }
while (true) {
- auto opted_msg = RecvConfUiMsg(hal_cli_socket_);
- if (!opted_msg) {
- ConfUiLog(ERROR) << "Error in RecvConfUiMsg from HAL";
+ if (!hal_cli_socket_->IsOpen()) {
+ ConfUiLog(DEBUG) << "client is disconnected";
+ std::unique_lock<std::mutex> lk(socket_flag_mtx_);
+ hal_cli_socket_ = EstablishHalConnection();
+ is_socket_ok_ = true;
continue;
}
- auto input = std::move(opted_msg.value());
- input_multiplexer_.Push(hal_cmd_q_id_, std::move(input));
+ auto msg = RecvConfUiMsg(hal_cli_socket_);
+ if (!msg) {
+ ConfUiLog(ERROR) << "Error in RecvConfUiMsg from HAL";
+ hal_cli_socket_->Close();
+ is_socket_ok_ = false;
+ continue;
+ }
+ /*
+ * In case of Vts test, the msg could be a user input. For now, we do not
+ * enforce the input grace period for Vts. However, if ever we do, here is
+ * where the time point check should happen. Once it is enqueued, it is not
+ * always guaranteed to be picked up reasonably soon.
+ */
+ constexpr bool is_secure = false;
+ auto to_override_if_user_input = WrapWithSecureFlag(*msg, is_secure);
+ if (to_override_if_user_input) {
+ msg = std::move(to_override_if_user_input);
+ }
+ input_multiplexer_.Push(hal_cmd_q_id_, std::move(msg));
}
}
-bool HostServer::SendUserSelection(UserResponse::type selection) {
- if (!curr_session_) {
- ConfUiLog(FATAL) << "Current session must not be null";
- return false;
- }
- if (curr_session_->GetState() != MainLoopState::kInSession) {
+void HostServer::SendUserSelection(std::unique_ptr<ConfUiMessage>& input) {
+ if (!curr_session_ ||
+ curr_session_->GetState() != MainLoopState::kInSession ||
+ !curr_session_->IsReadyForUserInput()) {
// ignore
- return true;
- }
-
- std::lock_guard<std::mutex> lock(input_socket_mtx_);
- if (selection != UserResponse::kConfirm &&
- selection != UserResponse::kCancel) {
- ConfUiLog(FATAL) << selection << " must be either" << UserResponse::kConfirm
- << "or" << UserResponse::kCancel;
- return false; // not reaching here
- }
-
- ConfUiMessage input{GetCurrentSessionId(),
- ToString(ConfUiCmd::kUserInputEvent), selection};
-
- input_multiplexer_.Push(user_input_evt_q_id_, std::move(input));
- return true;
-}
-
-void HostServer::PressConfirmButton(const bool is_down) {
- if (!is_down) {
return;
}
- // shared by N vnc/webRTC clients
- SendUserSelection(UserResponse::kConfirm);
+ constexpr bool is_secure = true;
+ auto secure_input = WrapWithSecureFlag(*input, is_secure);
+ input_multiplexer_.Push(user_input_evt_q_id_, std::move(secure_input));
}
-void HostServer::PressCancelButton(const bool is_down) {
- if (!is_down) {
+void HostServer::TouchEvent(const int x, const int y, const bool is_down) {
+ if (!is_down || !curr_session_) {
return;
}
- // shared by N vnc/webRTC clients
- SendUserSelection(UserResponse::kCancel);
+ std::unique_ptr<ConfUiMessage> input =
+ std::make_unique<ConfUiUserTouchMessage>(GetCurrentSessionId(), x, y);
+ constexpr bool is_secure = true;
+ auto secure_input = WrapWithSecureFlag(*input, is_secure);
+ SendUserSelection(secure_input);
+}
+
+void HostServer::UserAbortEvent() {
+ if (!curr_session_) {
+ return;
+ }
+ std::unique_ptr<ConfUiMessage> input =
+ std::make_unique<ConfUiUserSelectionMessage>(GetCurrentSessionId(),
+ UserResponse::kUserAbort);
+ constexpr bool is_secure = true;
+ auto secure_input = WrapWithSecureFlag(*input, is_secure);
+ SendUserSelection(secure_input);
}
bool HostServer::IsConfUiActive() {
@@ -141,119 +181,105 @@
}
SharedFD HostServer::EstablishHalConnection() {
- ConfUiLog(DEBUG) << "Waiting hal accepting";
- auto new_cli = SharedFD::Accept(*guest_hal_socket_);
- ConfUiLog(DEBUG) << "hal client accepted";
- return new_cli;
-}
-
-std::unique_ptr<Session> HostServer::ComputeCurrentSession(
- const std::string& session_id) {
- if (curr_session_ && (GetCurrentSessionId() != session_id)) {
- ConfUiLog(FATAL) << curr_session_->GetId() << " is active and in the"
- << GetCurrentState() << "but HAL sends command to"
- << session_id;
+ using namespace std::chrono_literals;
+ while (true) {
+ ConfUiLog(VERBOSE) << "Waiting hal accepting";
+ auto new_cli = SharedFD::Accept(*guest_hal_socket_);
+ ConfUiLog(VERBOSE) << "hal client accepted";
+ if (new_cli->IsOpen()) {
+ return new_cli;
+ }
+ std::this_thread::sleep_for(500ms);
}
- if (curr_session_) {
- return std::move(curr_session_);
- }
-
- // pick up a new session, or create one
- auto result = GetSession(session_id);
- if (result) {
- return std::move(result);
- }
-
- auto raw_ptr = new Session(session_id, display_num_, renderer_,
- host_mode_ctrl_, screen_connector_);
- result = std::unique_ptr<Session>(raw_ptr);
- // note that the new session is directly going to curr_session_
- // when it is suspended, it will be moved to session_map_
- return std::move(result);
}
// read the comments in the header file
[[noreturn]] void HostServer::MainLoop() {
while (true) {
// this gets one input from either queue:
- // from HAL or from all webrtc/vnc clients
+ // from HAL or from all webrtc clients
// if no input, sleep until there is
- const auto input = input_multiplexer_.Pop();
- const auto& [session_id, cmd_str, additional_info] = input;
+ auto input_ptr = input_multiplexer_.Pop();
+ auto& input = *input_ptr;
+ const auto session_id = input.GetSessionId();
+ const auto cmd = input.GetType();
+ const std::string cmd_str(ToString(cmd));
// take input for the Finite States Machine below
- const ConfUiCmd cmd = ToCmd(cmd_str);
- const bool is_user_input = (cmd == ConfUiCmd::kUserInputEvent);
- std::string src = is_user_input ? "input" : "hal";
+ std::string src = input.IsUserInput() ? "input" : "hal";
+ ConfUiLog(VERBOSE) << "In Session " << GetCurrentSessionId() << ", "
+ << "in state " << GetCurrentState() << ", "
+ << "received input from " << src << " cmd =" << cmd_str
+ << " going to session " << session_id;
- ConfUiLog(DEBUG) << "In Session" << GetCurrentSessionId() << ","
- << "in state" << GetCurrentState() << ","
- << "received input from" << src << "cmd =" << cmd_str
- << "and additional_info =" << additional_info
- << "going to session" << session_id;
-
- FsmInput fsm_input = ToFsmInput(input);
-
- if (is_user_input && !curr_session_) {
- // discard the input, there's no session to take it yet
- // actually, no confirmation UI screen is available
- ConfUiLog(DEBUG) << "Took user input but no active session is available.";
- continue;
- }
-
- /**
- * if the curr_session_ is null, create one
- * if the curr_session_ is not null but the session id doesn't match,
- * something is wrong. Confirmation UI doesn't allow preemption by
- * another confirmation UI session back to back. When it's preempted,
- * HAL must send "kSuspend"
- *
- */
- curr_session_ = ComputeCurrentSession(session_id);
- ConfUiLog(DEBUG) << "Host service picked up "
- << (curr_session_ ? curr_session_->GetId()
- : "null session");
- ConfUiLog(DEBUG) << "The state of current session is "
- << (curr_session_ ? ToString(curr_session_->GetState())
- : "null session");
-
- if (is_user_input) {
- curr_session_->Transition(is_user_input, hal_cli_socket_, fsm_input,
- additional_info);
- } else {
- ConfUiCmd cmd = ToCmd(cmd_str);
- switch (cmd) {
- case ConfUiCmd::kSuspend:
- curr_session_->Suspend(hal_cli_socket_);
- break;
- case ConfUiCmd::kRestore:
- curr_session_->Restore(hal_cli_socket_);
- break;
- case ConfUiCmd::kAbort:
- curr_session_->Abort(hal_cli_socket_);
- break;
- default:
- curr_session_->Transition(is_user_input, hal_cli_socket_, fsm_input,
- additional_info);
- break;
+ if (!curr_session_) {
+ if (cmd != ConfUiCmd::kStart) {
+ ConfUiLog(VERBOSE) << ToString(cmd) << " to " << session_id
+ << " is ignored as there is no session to receive";
+ continue;
}
+ // the session is created as kInit
+ curr_session_ = CreateSession(input.GetSessionId());
}
-
- // check the session if it is inactive (e.g. init, suspended)
- // and if it is done (transitioned to init from any other state)
- if (curr_session_->IsSuspended()) {
- session_map_[GetCurrentSessionId()] = std::move(curr_session_);
- curr_session_ = nullptr;
- continue;
+ if (cmd == ConfUiCmd::kUserTouchEvent) {
+ ConfUiSecureUserTouchMessage& touch_event =
+ static_cast<ConfUiSecureUserTouchMessage&>(input);
+ auto [x, y] = touch_event.GetLocation();
+ const bool is_confirm = curr_session_->IsConfirm(x, y);
+ const bool is_cancel = curr_session_->IsCancel(x, y);
+ if (!is_confirm && !is_cancel) {
+ // ignore, take the next input
+ continue;
+ }
+ decltype(input_ptr) tmp_input_ptr =
+ std::make_unique<ConfUiUserSelectionMessage>(
+ GetCurrentSessionId(),
+ (is_confirm ? UserResponse::kConfirm : UserResponse::kCancel));
+ input_ptr = WrapWithSecureFlag(*tmp_input_ptr, touch_event.IsSecure());
}
+ Transition(input_ptr);
- if (curr_session_->GetState() == MainLoopState::kAwaitCleanup) {
+ // finalize
+ if (curr_session_ &&
+ curr_session_->GetState() == MainLoopState::kAwaitCleanup) {
curr_session_->CleanUp();
curr_session_ = nullptr;
}
- // otherwise, continue with keeping the curr_session_
} // end of the infinite while loop
}
+std::shared_ptr<Session> HostServer::CreateSession(const std::string& name) {
+ return std::make_shared<Session>(name, display_num_, host_mode_ctrl_,
+ screen_connector_);
+}
+
+static bool IsUserAbort(ConfUiMessage& msg) {
+ if (msg.GetType() != ConfUiCmd::kUserInputEvent) {
+ return false;
+ }
+ ConfUiUserSelectionMessage& selection =
+ static_cast<ConfUiUserSelectionMessage&>(msg);
+ return (selection.GetResponse() == UserResponse::kUserAbort);
+}
+
+void HostServer::Transition(std::unique_ptr<ConfUiMessage>& input_ptr) {
+ auto& input = *input_ptr;
+ const auto session_id = input.GetSessionId();
+ const auto cmd = input.GetType();
+ const std::string cmd_str(ToString(cmd));
+ FsmInput fsm_input = ToFsmInput(input);
+ ConfUiLog(VERBOSE) << "Handling " << ToString(cmd);
+ if (IsUserAbort(input)) {
+ curr_session_->UserAbort(hal_cli_socket_);
+ return;
+ }
+
+ if (cmd == ConfUiCmd::kAbort) {
+ curr_session_->Abort();
+ return;
+ }
+ curr_session_->Transition(hal_cli_socket_, fsm_input, input);
+}
+
} // end of namespace confui
} // end of namespace cuttlefish
diff --git a/host/libs/confui/host_server.h b/host/libs/confui/host_server.h
index be5caeb..904af33 100644
--- a/host/libs/confui/host_server.h
+++ b/host/libs/confui/host_server.h
@@ -35,7 +35,6 @@
#include "host/commands/kernel_log_monitor/utils.h"
#include "host/libs/config/logging.h"
#include "host/libs/confui/host_mode_ctrl.h"
-#include "host/libs/confui/host_renderer.h"
#include "host/libs/confui/host_virtual_input.h"
#include "host/libs/confui/server_common.h"
#include "host/libs/confui/session.h"
@@ -50,11 +49,11 @@
cuttlefish::ScreenConnectorFrameRenderer& screen_connector);
void Start(); // start this server itself
- virtual ~HostServer() = default;
+ virtual ~HostServer() {}
- // implement input interfaces. called by webRTC & vnc
- void PressConfirmButton(const bool is_down) override;
- void PressCancelButton(const bool is_down) override;
+ // implement input interfaces. called by webRTC
+ void TouchEvent(const int x, const int y, const bool is_down) override;
+ void UserAbortEvent() override;
bool IsConfUiActive() override;
private:
@@ -89,7 +88,7 @@
* should render the Android guest frames but keep the confirmation
* UI session and frame
*
- * The inputs are I = {u, g}. 'u' is the user input from vnc/webRTC
+ * The inputs are I = {u, g}. 'u' is the user input from webRTC
* clients. Note that the host service serialized the concurrent user
* inputs from multiple clients. 'g' is the command from the HAL service
*
@@ -115,21 +114,10 @@
SharedFD EstablishHalConnection();
- // failed to start dialog, etc
- // basically, will reset the session, so start from the beginning in the same
- // session
- void ResetOnCommandFailure();
+ std::shared_ptr<Session> CreateSession(const std::string& session_name);
+ void SendUserSelection(std::unique_ptr<ConfUiMessage>& input);
- // note: the picked session will be removed from session_map_
- std::unique_ptr<Session> GetSession(const std::string& session_id) {
- if (session_map_.find(session_id) == session_map_.end()) {
- return nullptr;
- }
- std::unique_ptr<Session> temp = std::move(session_map_[session_id]);
- session_map_.erase(session_id);
- return temp;
- }
-
+ void Transition(std::unique_ptr<ConfUiMessage>& input_ptr);
std::string GetCurrentSessionId() {
if (curr_session_) {
return curr_session_->GetId();
@@ -143,29 +131,23 @@
}
return ToString(curr_session_->GetState());
}
- std::unique_ptr<Session> ComputeCurrentSession(const std::string& session_id);
- bool SendUserSelection(UserResponse::type selection);
const std::uint32_t display_num_;
HostModeCtrl& host_mode_ctrl_;
ScreenConnectorFrameRenderer& screen_connector_;
- // this member creates a raw frame
- ConfUiRenderer renderer_;
-
std::string input_socket_path_;
- std::string hal_socket_path_;
+ int hal_vsock_port_;
- // session id to Session object map, for those that are suspended
- std::unordered_map<std::string, std::unique_ptr<Session>> session_map_;
- // curr_session_ doesn't belong to session_map_
- std::unique_ptr<Session> curr_session_;
+ std::shared_ptr<Session> curr_session_;
SharedFD guest_hal_socket_;
// ACCEPTED fd on guest_hal_socket_
SharedFD hal_cli_socket_;
- std::mutex input_socket_mtx_;
+ using Multiplexer =
+ Multiplexer<std::unique_ptr<ConfUiMessage>,
+ ThreadSafeQueue<std::unique_ptr<ConfUiMessage>>>;
/*
* Multiplexer has N queues. When pop(), it is going to sleep until
* there's at least one item in at least one queue. The lower the Q
@@ -174,12 +156,16 @@
* For HostServer, we have a queue for the user input events, and
* another for hal cmd/msg queues
*/
- Multiplexer<ConfUiMessage> input_multiplexer_;
+ Multiplexer input_multiplexer_;
int hal_cmd_q_id_; // Q id in input_multiplexer_
int user_input_evt_q_id_; // Q id in input_multiplexer_
std::thread main_loop_thread_;
std::thread hal_input_fetcher_thread_;
+
+ std::mutex socket_flag_mtx_;
+ std::condition_variable socket_flag_cv_;
+ bool is_socket_ok_;
};
} // end of namespace confui
diff --git a/host/libs/confui/host_virtual_input.h b/host/libs/confui/host_virtual_input.h
index ec800cb..65ec577 100644
--- a/host/libs/confui/host_virtual_input.h
+++ b/host/libs/confui/host_virtual_input.h
@@ -23,13 +23,13 @@
enum class ConfUiKeys : std::uint32_t { Confirm = 7, Cancel = 8 };
/**
- * vnc, webrtc will deliver the user inputs from their client
+ * webrtc will deliver the user inputs from their client
* to this class object
*/
class HostVirtualInput {
public:
- virtual void PressConfirmButton(const bool is_down) = 0;
- virtual void PressCancelButton(const bool is_down) = 0;
+ virtual void TouchEvent(const int x, const int y, const bool is_down) = 0;
+ virtual void UserAbortEvent() = 0;
virtual ~HostVirtualInput() = default;
// guarantees that if this returns true, it is confirmation UI mode
virtual bool IsConfUiActive() = 0;
diff --git a/host/libs/confui/layouts/layout.h b/host/libs/confui/layouts/layout.h
index bb40b67..4f6c4dc 100644
--- a/host/libs/confui/layouts/layout.h
+++ b/host/libs/confui/layouts/layout.h
@@ -29,38 +29,13 @@
DECLARE_PARAMETER(RightEdgeOfScreen);
DECLARE_PARAMETER(BottomOfScreen);
-DECLARE_PARAMETER(PowerButtonTop);
-DECLARE_PARAMETER(PowerButtonBottom);
-DECLARE_PARAMETER(VolUpButtonTop);
-DECLARE_PARAMETER(VolUpButtonBottom);
DECLARE_PARAMETER(DefaultFontSize); // 14_dp regular and 18_dp magnified
DECLARE_PARAMETER(BodyFontSize); // 16_dp regular and 20_dp magnified
DECLARE_TYPED_PARAMETER(ShieldColor, ::teeui::Color);
DECLARE_TYPED_PARAMETER(ColorText, ::teeui::Color);
DECLARE_TYPED_PARAMETER(ColorBG, ::teeui::Color);
-NEW_PARAMETER_SET(ConUIParameters, RightEdgeOfScreen, BottomOfScreen,
- PowerButtonTop, PowerButtonBottom, VolUpButtonTop,
- VolUpButtonBottom, DefaultFontSize, BodyFontSize, ShieldColor,
- ColorText, ColorBG);
-
CONSTANT(BorderWidth, 24_dp);
-CONSTANT(PowerButtonCenter, (PowerButtonTop() + PowerButtonBottom()) / 2_px);
-CONSTANT(VolUpButtonCenter, (VolUpButtonTop() + VolUpButtonBottom()) / 2.0_px);
-CONSTANT(GrayZone, 12_dp);
-CONSTANT(RightLabelEdge, RightEdgeOfScreen() - BorderWidth - GrayZone);
-CONSTANT(LabelWidth, RightLabelEdge - BorderWidth);
-
-CONSTANT(SQRT2, 1.4142135623_dp);
-CONSTANT(SQRT8, 2.828427125_dp);
-
-CONSTANT(ARROW_SHAPE,
- CONVEX_OBJECTS(
- CONVEX_OBJECT(Vec2d{.0_dp, .0_dp}, Vec2d{6.0_dp, 6.0_dp},
- Vec2d{6.0_dp - SQRT8, 6.0_dp}, Vec2d{-SQRT2, SQRT2}),
- CONVEX_OBJECT(Vec2d{6.0_dp - SQRT8, 6.0_dp}, Vec2d{6.0_dp, 6.0_dp},
- Vec2d{0.0_dp, 12.0_dp},
- Vec2d{-SQRT2, 12.0_dp - SQRT2})));
DECLARE_FONT_BUFFER(RobotoMedium, RobotoMedium, RobotoMedium_length);
DECLARE_FONT_BUFFER(RobotoRegular, RobotoRegular, RobotoRegular_length);
@@ -68,63 +43,32 @@
CONSTANT(DefaultFont, FONT(RobotoRegular));
-BEGIN_ELEMENT(LabelOK, teeui::Label)
-FontSize(DefaultFontSize());
-LineHeight(20_dp);
-NumberOfLines(2);
-Dimension(LabelWidth, HeightFromLines);
-Position(BorderWidth, PowerButtonCenter - dim_h / 2.0_px);
-DefaultText("Press Power Button Confirm");
-RightJustified;
-VerticallyCentered;
-TextColor(ColorText());
-Font(FONT(RobotoMedium));
-TextID(TEXT_ID(TranslationId::CONFIRM_PWR_BUTTON_DOUBLE_PRESS));
-END_ELEMENT();
+DECLARE_TYPED_PARAMETER(ColorButton, ::teeui::Color);
-BEGIN_ELEMENT(IconPower, teeui::Button, ConvexObjectCount(2))
-Dimension(BorderWidth, PowerButtonBottom() - PowerButtonTop());
-Position(RightEdgeOfScreen() - BorderWidth, PowerButtonTop());
-CornerRadius(3_dp);
-ButtonColor(ColorText());
-RoundTopLeft;
-RoundBottomLeft;
-ConvexObjectColor(ColorBG());
-ConvexObjects(ARROW_SHAPE);
-END_ELEMENT();
+NEW_PARAMETER_SET(ConfUIParameters, RightEdgeOfScreen, BottomOfScreen,
+ DefaultFontSize, BodyFontSize, ShieldColor, ColorText,
+ ColorBG, ColorButton);
-BEGIN_ELEMENT(LabelCancel, teeui::Label)
-FontSize(DefaultFontSize());
-LineHeight(20_dp);
-NumberOfLines(2);
-Dimension(LabelWidth, HeightFromLines);
-Position(BorderWidth, VolUpButtonCenter - dim_h / 2.0_px);
-DefaultText("Press Menu Button to Cancel");
-RightJustified;
-VerticallyCentered;
-TextColor(ColorText());
-Font(FONT(RobotoMedium));
-TextID(TEXT_ID(TranslationId::CANCEL));
-END_ELEMENT();
-
-BEGIN_ELEMENT(IconVolUp, teeui::Button, ConvexObjectCount(2))
-Dimension(BorderWidth, VolUpButtonBottom() - VolUpButtonTop());
-Position(RightEdgeOfScreen() - BorderWidth, VolUpButtonTop());
-CornerRadius(5_dp);
-ButtonColor(ColorBG());
-ConvexObjectColor(ColorText());
-ConvexObjects(ARROW_SHAPE);
-END_ELEMENT();
+CONSTANT(IconShieldDistanceFromTop, 100_dp);
+CONSTANT(LabelBorderZone, 4_dp);
+CONSTANT(RightLabelEdge, RightEdgeOfScreen() - BorderWidth);
+CONSTANT(LabelWidth, RightLabelEdge - BorderWidth);
+CONSTANT(ButtonHeight, 72_dp);
+CONSTANT(ButtonPositionX, 0);
+CONSTANT(ButtonPositionY, BottomOfScreen() - ButtonHeight);
+CONSTANT(ButtonWidth, 130_dp);
+CONSTANT(ButtonLabelDistance, 12_dp);
BEGIN_ELEMENT(IconShield, teeui::Label)
FontSize(24_dp);
LineHeight(24_dp);
NumberOfLines(1);
Dimension(LabelWidth, HeightFromLines);
-Position(BorderWidth, BOTTOM_EDGE_OF(LabelCancel) + 40_dp);
+Position(BorderWidth, IconShieldDistanceFromTop);
DefaultText(
"A"); // ShieldTTF has just one glyph at the code point for capital A
TextColor(ShieldColor());
+HorizontalTextAlignment(Alignment::CENTER);
Font(FONT(Shield));
END_ELEMENT();
@@ -132,8 +76,8 @@
FontSize(20_dp);
LineHeight(20_dp);
NumberOfLines(1);
-Dimension(RightEdgeOfScreen() - BorderWidth, HeightFromLines);
-Position(BorderWidth, BOTTOM_EDGE_OF(IconShield) + 12_dp);
+Dimension(LabelWidth, HeightFromLines);
+Position(BorderWidth, BOTTOM_EDGE_OF(IconShield) + 16_dp);
DefaultText("Android Protected Confirmation");
Font(FONT(RobotoMedium));
VerticallyCentered;
@@ -141,12 +85,56 @@
TextID(TEXT_ID(TranslationId::TITLE));
END_ELEMENT();
+BEGIN_ELEMENT(IconOk, teeui::Button, ConvexObjectCount(1))
+Dimension(ButtonWidth, ButtonHeight - BorderWidth);
+Position(RightEdgeOfScreen() - ButtonWidth - BorderWidth,
+ ButtonPositionY + ButtonLabelDistance);
+CornerRadius(4_dp);
+ButtonColor(ColorButton());
+RoundTopLeft;
+RoundBottomLeft;
+RoundTopRight;
+RoundBottomRight;
+END_ELEMENT();
+
+BEGIN_ELEMENT(LabelOK, teeui::Label)
+FontSize(BodyFontSize());
+LineHeight(BodyFontSize() * 1.4_px);
+NumberOfLines(1);
+Dimension(ButtonWidth - (LabelBorderZone * 2_dp),
+ ButtonHeight - BorderWidth - (LabelBorderZone * 2_dp));
+Position(RightEdgeOfScreen() - ButtonWidth - BorderWidth + LabelBorderZone,
+ ButtonPositionY + ButtonLabelDistance + LabelBorderZone);
+DefaultText("Confirm");
+Font(FONT(RobotoMedium));
+HorizontalTextAlignment(Alignment::CENTER);
+VerticalTextAlignment(Alignment::CENTER);
+TextColor(ColorBG());
+TextID(TEXT_ID(TranslationId::CONFIRM));
+END_ELEMENT();
+
+BEGIN_ELEMENT(LabelCancel, teeui::Label)
+FontSize(BodyFontSize());
+LineHeight(BodyFontSize() * 1.4_px);
+NumberOfLines(1);
+Dimension(ButtonWidth - (LabelBorderZone * 2_dp),
+ ButtonHeight - BorderWidth - (LabelBorderZone * 2_dp));
+Position(BorderWidth + LabelBorderZone,
+ ButtonPositionY + ButtonLabelDistance + LabelBorderZone);
+DefaultText("Cancel");
+HorizontalTextAlignment(Alignment::LEFT);
+Font(FONT(RobotoMedium));
+VerticallyCentered;
+TextColor(ColorButton());
+TextID(TEXT_ID(TranslationId::CANCEL));
+END_ELEMENT();
+
BEGIN_ELEMENT(LabelHint, teeui::Label)
FontSize(DefaultFontSize());
LineHeight(DefaultFontSize() * 1.5_px);
NumberOfLines(4);
Dimension(LabelWidth, HeightFromLines);
-Position(BorderWidth, BottomOfScreen() - BorderWidth - dim_h);
+Position(BorderWidth, ButtonPositionY - dim_h - 48_dp);
DefaultText(
"This confirmation provides an extra layer of security for the action "
"you're "
@@ -161,7 +149,7 @@
FontSize(BodyFontSize());
LineHeight(BodyFontSize() * 1.4_px);
NumberOfLines(20);
-Position(BorderWidth, BOTTOM_EDGE_OF(LabelTitle) + 18_dp);
+Position(BorderWidth, BOTTOM_EDGE_OF(LabelTitle) + 16_dp);
Dimension(LabelWidth, LabelHint::pos_y - pos_y - 24_dp);
DefaultText(
"12345678901234567890123456789012345678901234567890123456789012345678901234"
@@ -171,7 +159,7 @@
Font(FONT(RobotoRegular));
END_ELEMENT();
-NEW_LAYOUT(ConfUILayout, LabelOK, IconPower, LabelCancel, IconVolUp, IconShield,
- LabelTitle, LabelHint, LabelBody);
+NEW_LAYOUT(ConfUILayout, IconShield, LabelTitle, LabelHint, LabelBody, IconOk,
+ LabelOK, LabelCancel);
} // namespace teeui
diff --git a/host/libs/confui/secure_input.cc b/host/libs/confui/secure_input.cc
new file mode 100644
index 0000000..672ed3d
--- /dev/null
+++ b/host/libs/confui/secure_input.cc
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "host/libs/confui/secure_input.h"
+
+namespace cuttlefish {
+namespace confui {
+
+std::unique_ptr<ConfUiSecureUserSelectionMessage> ToSecureSelectionMessage(
+ const ConfUiUserSelectionMessage& msg, const bool secure) {
+ return std::make_unique<ConfUiSecureUserSelectionMessage>(msg, secure);
+}
+
+std::unique_ptr<ConfUiSecureUserTouchMessage> ToSecureTouchMessage(
+ const ConfUiUserTouchMessage& msg, const bool secure) {
+ return std::make_unique<ConfUiSecureUserTouchMessage>(msg, secure);
+}
+
+} // end of namespace confui
+} // end of namespace cuttlefish
diff --git a/host/libs/confui/secure_input.h b/host/libs/confui/secure_input.h
new file mode 100644
index 0000000..527fcd7
--- /dev/null
+++ b/host/libs/confui/secure_input.h
@@ -0,0 +1,77 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <memory>
+
+#include "common/libs/confui/confui.h"
+
+/** ConfUiUserSelectionMessage with a security flag
+ *
+ * Inputs generated by something that belong to (virtualized) TEE is regarded
+ * as secure. Otherwise (e.g. inputs generated by the guest calling
+ * deliverSecureInputEvent), it is regarded as insecure.
+ *
+ * The host marks the security field, and use it internally and exclusively.
+ *
+ */
+namespace cuttlefish {
+namespace confui {
+class ConfUiSecureUserSelectionMessage : public ConfUiMessage {
+ public:
+ ConfUiSecureUserSelectionMessage(const ConfUiUserSelectionMessage& msg,
+ const bool secure)
+ : ConfUiMessage(msg.GetSessionId()), msg_(msg), is_secure_(secure) {}
+ ConfUiSecureUserSelectionMessage() = delete;
+ virtual ~ConfUiSecureUserSelectionMessage() = default;
+ std::string ToString() const override { return msg_.ToString(); }
+ ConfUiCmd GetType() const override { return msg_.GetType(); }
+ auto GetResponse() const { return msg_.GetResponse(); }
+ // SendOver is between guest and host, so it doesn't send the is_secure_
+ bool SendOver(SharedFD fd) override { return msg_.SendOver(fd); }
+ bool IsSecure() const { return is_secure_; }
+ // SetSecure() might be needed later on but not now.
+
+ private:
+ ConfUiUserSelectionMessage msg_;
+ bool is_secure_;
+};
+
+class ConfUiSecureUserTouchMessage : public ConfUiMessage {
+ public:
+ ConfUiSecureUserTouchMessage(const ConfUiUserTouchMessage& msg,
+ const bool secure)
+ : ConfUiMessage(msg.GetSessionId()), msg_(msg), is_secure_(secure) {}
+ virtual ~ConfUiSecureUserTouchMessage() = default;
+ std::string ToString() const override { return msg_.ToString(); }
+ ConfUiCmd GetType() const override { return msg_.GetType(); }
+ auto GetResponse() const { return msg_.GetResponse(); }
+ bool SendOver(SharedFD fd) override { return msg_.SendOver(fd); }
+ std::pair<int, int> GetLocation() { return msg_.GetLocation(); }
+ bool IsSecure() const { return is_secure_; }
+
+ private:
+ ConfUiUserTouchMessage msg_;
+ bool is_secure_;
+};
+
+std::unique_ptr<ConfUiSecureUserSelectionMessage> ToSecureSelectionMessage(
+ const ConfUiUserSelectionMessage& msg, const bool secure);
+std::unique_ptr<ConfUiSecureUserTouchMessage> ToSecureTouchMessage(
+ const ConfUiUserTouchMessage& msg, const bool secure);
+} // end of namespace confui
+} // end of namespace cuttlefish
diff --git a/host/libs/confui/server_common.cc b/host/libs/confui/server_common.cc
index cf46fe3..66adb55 100644
--- a/host/libs/confui/server_common.cc
+++ b/host/libs/confui/server_common.cc
@@ -17,60 +17,37 @@
#include "host/libs/confui/server_common.h"
namespace cuttlefish {
namespace confui {
-static FsmInput UserEvtToFsmInput(UserResponse::type user_response) {
- if (user_response == UserResponse::kConfirm) {
- return FsmInput::kUserUnknown;
- }
- if (user_response == UserResponse::kCancel) {
- return FsmInput::kUserCancel;
- }
- return FsmInput::kUserUnknown;
-}
-
-FsmInput ToFsmInput(const ConfUiMessage& confui_msg) {
- ConfUiCmd cmd = ToCmd(confui_msg.type_);
- if (cmd == ConfUiCmd::kUserInputEvent) {
- return UserEvtToFsmInput(confui_msg.msg_);
- }
- const auto hal_cmd = cmd;
- switch (hal_cmd) {
+FsmInput ToFsmInput(const ConfUiMessage& msg) {
+ const auto cmd = msg.GetType();
+ switch (cmd) {
+ case ConfUiCmd::kUserInputEvent:
+ return FsmInput::kUserEvent;
case ConfUiCmd::kUnknown:
return FsmInput::kHalUnknown;
case ConfUiCmd::kStart:
return FsmInput::kHalStart;
case ConfUiCmd::kStop:
return FsmInput::kHalStop;
- case ConfUiCmd::kSuspend:
- return FsmInput::kHalSuspend;
- case ConfUiCmd::kRestore:
- return FsmInput::kHalRestore;
case ConfUiCmd::kAbort:
return FsmInput::kHalAbort;
case ConfUiCmd::kCliAck:
case ConfUiCmd::kCliRespond:
default:
- ConfUiLog(FATAL) << "The" << ToString(hal_cmd)
- << "is not handled by Session";
+ ConfUiLog(FATAL) << "The" << ToString(cmd)
+ << "is not handled by the Session FSM but "
+ << "directly calls Abort()";
}
return FsmInput::kHalUnknown;
}
std::string ToString(FsmInput input) {
switch (input) {
- case FsmInput::kUserConfirm:
- return {"kUserConfirm"};
- case FsmInput::kUserCancel:
- return {"kUserCancel"};
- case FsmInput::kUserUnknown:
- return {"kUserUnknown"};
+ case FsmInput::kUserEvent:
+ return {"kUserEvent"};
case FsmInput::kHalStart:
return {"kHalStart"};
case FsmInput::kHalStop:
return {"kHalStop"};
- case FsmInput::kHalSuspend:
- return {"kHalSuspend"};
- case FsmInput::kHalRestore:
- return {"kHalRestore"};
case FsmInput::kHalAbort:
return {"kHalAbort"};
case FsmInput::kHalUnknown:
@@ -88,10 +65,10 @@
return "kInSession";
case MainLoopState::kWaitStop:
return "kWaitStop";
- case MainLoopState::kSuspended:
- return "kSuspended";
case MainLoopState::kAwaitCleanup:
return "kAwaitCleanup";
+ case MainLoopState::kTerminated:
+ return "kTerminated";
default:
return "kInvalid";
}
diff --git a/host/libs/confui/server_common.h b/host/libs/confui/server_common.h
index 6175d16..8ffd578 100644
--- a/host/libs/confui/server_common.h
+++ b/host/libs/confui/server_common.h
@@ -17,6 +17,7 @@
#pragma once
#include <cstdint>
+#include <memory>
#include <vector>
#include "common/libs/confui/confui.h"
@@ -27,8 +28,8 @@
kInit = 1,
kInSession = 2,
kWaitStop = 3, // wait ack after sending confirm/cancel
- kSuspended = 4,
kAwaitCleanup = 5,
+ kTerminated = 8,
kInvalid = 9
};
@@ -36,13 +37,9 @@
// FSM input to Session FSM
enum class FsmInput : std::uint32_t {
- kUserConfirm,
- kUserCancel,
- kUserUnknown,
+ kUserEvent = 1,
kHalStart,
kHalStop,
- kHalSuspend,
- kHalRestore,
kHalAbort,
kHalUnknown
};
@@ -50,7 +47,9 @@
std::string ToString(FsmInput input);
std::string ToString(const MainLoopState& state);
-FsmInput ToFsmInput(const ConfUiMessage& confui_msg);
+FsmInput ToFsmInput(const ConfUiMessage& msg);
+std::unique_ptr<ConfUiMessage> CreateFromUserSelection(
+ const std::string& session_id, const UserResponse::type user_selection);
} // end of namespace confui
} // end of namespace cuttlefish
diff --git a/host/libs/confui/session.cc b/host/libs/confui/session.cc
index 891250d..2b7069b 100644
--- a/host/libs/confui/session.cc
+++ b/host/libs/confui/session.cc
@@ -16,22 +16,47 @@
#include "host/libs/confui/session.h"
+#include <algorithm>
+
+#include "host/libs/confui/secure_input.h"
+
namespace cuttlefish {
namespace confui {
-Session::Session(const std::string& session_id, const std::uint32_t display_num,
- ConfUiRenderer& host_renderer, HostModeCtrl& host_mode_ctrl,
+Session::Session(const std::string& session_name,
+ const std::uint32_t display_num, HostModeCtrl& host_mode_ctrl,
ScreenConnectorFrameRenderer& screen_connector,
const std::string& locale)
- : session_id_{session_id},
+ : session_id_{session_name},
display_num_{display_num},
- renderer_{host_renderer},
host_mode_ctrl_{host_mode_ctrl},
screen_connector_{screen_connector},
locale_{locale},
state_{MainLoopState::kInit},
saved_state_{MainLoopState::kInit} {}
+/** return grace period + alpha
+ *
+ * grace period is the gap between user seeing the dialog
+ * and the UI starts to take the user inputs
+ * Grace period should be at least 1s.
+ * Session requests the Renderer to render the dialog,
+ * but it might not be immediate. So, add alpha to 1s
+ */
+static const std::chrono::milliseconds GetGracePeriod() {
+ using std::literals::chrono_literals::operator""ms;
+ return 1000ms + 100ms;
+}
+
+bool Session::IsReadyForUserInput() const {
+ using std::literals::chrono_literals::operator""ms;
+ if (!start_time_) {
+ return false;
+ }
+ const auto right_now = Clock::now();
+ return (right_now - *start_time_) >= GetGracePeriod();
+}
+
bool Session::IsConfUiActive() const {
if (state_ == MainLoopState::kInSession ||
state_ == MainLoopState::kWaitStop) {
@@ -40,234 +65,241 @@
return false;
}
-bool Session::RenderDialog(const std::string& msg, const std::string& locale) {
- auto [teeui_frame, is_success] = renderer_.RenderRawFrame(msg, locale);
- if (!is_success) {
+template <typename C, typename T>
+static bool Contains(const C& c, T&& item) {
+ auto itr = std::find(c.begin(), c.end(), std::forward<T>(item));
+ return itr != c.end();
+}
+
+bool Session::IsInverted() const {
+ return Contains(ui_options_, teeui::UIOption::AccessibilityInverted);
+}
+
+bool Session::IsMagnified() const {
+ return Contains(ui_options_, teeui::UIOption::AccessibilityMagnified);
+}
+
+bool Session::RenderDialog() {
+ renderer_ = ConfUiRenderer::GenerateRenderer(
+ display_num_, prompt_text_, locale_, IsInverted(), IsMagnified());
+ if (!renderer_) {
return false;
}
- prompt_ = msg;
- locale_ = locale;
-
- ConfUiLog(DEBUG) << "actually trying to render the frame"
- << thread::GetName();
- auto frame_width = ScreenConnectorInfo::ScreenWidth(display_num_);
- auto frame_height = ScreenConnectorInfo::ScreenHeight(display_num_);
- auto frame_stride_bytes =
- ScreenConnectorInfo::ScreenStrideBytes(display_num_);
- auto frame_bytes = reinterpret_cast<std::uint8_t*>(teeui_frame.data());
+ auto teeui_frame = renderer_->RenderRawFrame();
+ if (!teeui_frame) {
+ return false;
+ }
+ ConfUiLog(VERBOSE) << "actually trying to render the frame"
+ << thread::GetName();
+ auto frame_width = teeui_frame->Width();
+ auto frame_height = teeui_frame->Height();
+ auto frame_stride_bytes = teeui_frame->ScreenStrideBytes();
+ auto frame_bytes = reinterpret_cast<std::uint8_t*>(teeui_frame->data());
return screen_connector_.RenderConfirmationUi(
display_num_, frame_width, frame_height, frame_stride_bytes, frame_bytes);
}
-bool Session::IsSuspended() const {
- return (state_ == MainLoopState::kSuspended);
-}
-
-MainLoopState Session::Transition(const bool is_user_input, SharedFD& hal_cli,
- const FsmInput fsm_input,
- const std::string& additional_info) {
+MainLoopState Session::Transition(SharedFD& hal_cli, const FsmInput fsm_input,
+ const ConfUiMessage& conf_ui_message) {
+ bool should_keep_running = false;
+ bool already_terminated = false;
switch (state_) {
case MainLoopState::kInit: {
- HandleInit(is_user_input, hal_cli, fsm_input, additional_info);
+ should_keep_running = HandleInit(hal_cli, fsm_input, conf_ui_message);
} break;
case MainLoopState::kInSession: {
- HandleInSession(is_user_input, hal_cli, fsm_input);
+ should_keep_running =
+ HandleInSession(hal_cli, fsm_input, conf_ui_message);
} break;
case MainLoopState::kWaitStop: {
- if (is_user_input) {
- ConfUiLog(DEBUG) << "User input ignored" << ToString(fsm_input) << " : "
- << additional_info << "at state" << ToString(state_);
+ if (IsUserInput(fsm_input)) {
+ ConfUiLog(VERBOSE) << "User input ignored " << ToString(fsm_input)
+ << " : " << ToString(conf_ui_message)
+ << " at the state " << ToString(state_);
}
- HandleWaitStop(is_user_input, hal_cli, fsm_input);
+ should_keep_running = HandleWaitStop(hal_cli, fsm_input);
+ } break;
+ case MainLoopState::kTerminated: {
+ already_terminated = true;
} break;
default:
- // host service explicitly calls restore and suspend
- ConfUiLog(FATAL) << "Must not be in the state of" << ToString(state_);
+ ConfUiLog(FATAL) << "Must not be in the state of " << ToString(state_);
break;
}
+ if (!should_keep_running && !already_terminated) {
+ ScheduleToTerminate();
+ }
return state_;
};
-bool Session::Suspend(SharedFD hal_cli) {
- if (state_ == MainLoopState::kInit) {
- // HAL sent wrong command
- ConfUiLog(FATAL)
- << "HAL sent wrong command, suspend, when the session is in kIinit";
- return false;
- }
- if (state_ == MainLoopState::kSuspended) {
- ConfUiLog(DEBUG) << "Already kSuspended state";
- return false;
- }
- saved_state_ = state_;
- state_ = MainLoopState::kSuspended;
- host_mode_ctrl_.SetMode(HostModeCtrl::ModeType::kAndroidMode);
- if (!packet::SendAck(hal_cli, session_id_, /*is success*/ true,
- "suspended")) {
- ConfUiLog(FATAL) << "I/O error";
- return false;
- }
- return true;
-}
-
-bool Session::Restore(SharedFD hal_cli) {
- if (state_ == MainLoopState::kInit) {
- // HAL sent wrong command
- ConfUiLog(FATAL)
- << "HAL sent wrong command, restore, when the session is in kIinit";
- return false;
- }
-
- if (state_ != MainLoopState::kSuspended) {
- ConfUiLog(DEBUG) << "Already Restored to state " + ToString(state_);
- return false;
- }
- host_mode_ctrl_.SetMode(HostModeCtrl::ModeType::kConfUI_Mode);
- if (!RenderDialog(prompt_, locale_)) {
- // the confirmation UI is driven by a user app, not running from the start
- // automatically so that means webRTC/vnc should have been set up
- ConfUiLog(ERROR) << "Dialog is not rendered. However, it should."
- << "No webRTC can't initiate any confirmation UI.";
- if (!packet::SendAck(hal_cli, session_id_, false,
- "render failed in restore")) {
- ConfUiLog(FATAL) << "Rendering failed in restore, and ack failed in I/O";
- }
- state_ = MainLoopState::kInit;
- return false;
- }
- if (!packet::SendAck(hal_cli, session_id_, true, "restored")) {
- ConfUiLog(FATAL) << "Ack to restore failed in I/O";
- }
- state_ = saved_state_;
- saved_state_ = MainLoopState::kInit;
- return true;
-}
-
-bool Session::Kill(SharedFD hal_cli, const std::string& response_msg) {
- state_ = MainLoopState::kAwaitCleanup;
- saved_state_ = MainLoopState::kInvalid;
- if (!packet::SendAck(hal_cli, session_id_, true, response_msg)) {
- ConfUiLog(FATAL) << "I/O error in ack to Abort";
- return false;
- }
- return true;
-}
-
void Session::CleanUp() {
if (state_ != MainLoopState::kAwaitCleanup) {
ConfUiLog(FATAL) << "Clean up a session only when in kAwaitCleanup";
}
+ state_ = MainLoopState::kTerminated;
// common action done when the state is back to init state
host_mode_ctrl_.SetMode(HostModeCtrl::ModeType::kAndroidMode);
}
-void Session::ReportErrorToHal(SharedFD hal_cli, const std::string& msg) {
- // reset the session -- destroy it & recreate it with the same
- // session id
+void Session::ScheduleToTerminate() {
state_ = MainLoopState::kAwaitCleanup;
- if (!packet::SendAck(hal_cli, session_id_, false, msg)) {
- ConfUiLog(FATAL) << "I/O error in sending ack to report rendering failure";
+ saved_state_ = MainLoopState::kInvalid;
+}
+
+bool Session::ReportErrorToHal(SharedFD hal_cli, const std::string& msg) {
+ ScheduleToTerminate();
+ if (!SendAck(hal_cli, session_id_, false, msg)) {
+ ConfUiLog(ERROR) << "I/O error in sending ack to report rendering failure";
+ return false;
}
+ return true;
+}
+
+void Session::Abort() {
+ ConfUiLog(VERBOSE) << "Abort is called";
+ ScheduleToTerminate();
return;
}
-bool Session::Abort(SharedFD hal_cli) { return Kill(hal_cli, "aborted"); }
+void Session::UserAbort(SharedFD hal_cli) {
+ ConfUiLog(VERBOSE) << "it is a user abort input.";
+ SendAbortCmd(hal_cli, GetId());
+ Abort();
+ ScheduleToTerminate();
+}
-void Session::HandleInit(const bool is_user_input, SharedFD hal_cli,
- const FsmInput fsm_input,
- const std::string& additional_info) {
- using namespace cuttlefish::confui::packet;
- if (is_user_input) {
+bool Session::HandleInit(SharedFD hal_cli, const FsmInput fsm_input,
+ const ConfUiMessage& conf_ui_message) {
+ if (IsUserInput(fsm_input)) {
// ignore user input
state_ = MainLoopState::kInit;
- return;
+ return true;
}
- ConfUiLog(DEBUG) << ToString(fsm_input) << "is handled in HandleInit";
+ ConfUiLog(VERBOSE) << ToString(fsm_input) << "is handled in HandleInit";
if (fsm_input != FsmInput::kHalStart) {
ConfUiLog(ERROR) << "invalid cmd for Init State:" << ToString(fsm_input);
- // reset the session -- destroy it & recreate it with the same
- // session id
- ReportErrorToHal(hal_cli, "wrong hal command");
- return;
+ // ReportErrorToHal returns true if error report was successful
+ // However, anyway we abort this session on the host
+ ReportErrorToHal(hal_cli, HostError::kSystemError);
+ return false;
}
// Start Session
- ConfUiLog(DEBUG) << "Sending ack to hal_cli: "
- << Enum2Base(ConfUiCmd::kCliAck);
+ ConfUiLog(VERBOSE) << "Sending ack to hal_cli: "
+ << Enum2Base(ConfUiCmd::kCliAck);
host_mode_ctrl_.SetMode(HostModeCtrl::ModeType::kConfUI_Mode);
- auto confirmation_msg = additional_info;
- if (!RenderDialog(confirmation_msg, locale_)) {
+
+ auto start_cmd_msg = static_cast<const ConfUiStartMessage&>(conf_ui_message);
+ prompt_text_ = start_cmd_msg.GetPromptText();
+ locale_ = start_cmd_msg.GetLocale();
+ extra_data_ = start_cmd_msg.GetExtraData();
+ ui_options_ = start_cmd_msg.GetUiOpts();
+
+ // cbor_ can be correctly created after the session received kStart cmd
+ // at runtime
+ cbor_ = std::make_unique<Cbor>(prompt_text_, extra_data_);
+ if (cbor_->IsMessageTooLong()) {
+ ConfUiLog(ERROR) << "The prompt text and extra_data are too long to be "
+ << "properly encoded.";
+ ReportErrorToHal(hal_cli, HostError::kMessageTooLongError);
+ return false;
+ }
+ if (cbor_->IsMalformedUtf8()) {
+ ConfUiLog(ERROR) << "The prompt text appears to have incorrect UTF8 format";
+ ReportErrorToHal(hal_cli, HostError::kIncorrectUTF8);
+ return false;
+ }
+ if (!cbor_->IsOk()) {
+ ConfUiLog(ERROR) << "Unknown Error in cbor implementation";
+ ReportErrorToHal(hal_cli, HostError::kSystemError);
+ return false;
+ }
+
+ if (!RenderDialog()) {
// the confirmation UI is driven by a user app, not running from the start
- // automatically so that means webRTC/vnc should have been set up
+ // automatically so that means webRTC should have been set up
ConfUiLog(ERROR) << "Dialog is not rendered. However, it should."
<< "No webRTC can't initiate any confirmation UI.";
- ReportErrorToHal(hal_cli, "rendering failed");
- return;
+ ReportErrorToHal(hal_cli, HostError::kUIError);
+ return false;
}
- if (!packet::SendAck(hal_cli, session_id_, true, "started")) {
- ConfUiLog(FATAL) << "Ack to kStart failed in I/O";
+ start_time_ = std::make_unique<TimePoint>(std::move(Clock::now()));
+ if (!SendAck(hal_cli, session_id_, true, "started")) {
+ ConfUiLog(ERROR) << "Ack to kStart failed in I/O";
+ return false;
}
state_ = MainLoopState::kInSession;
- return;
+ return true;
}
-void Session::HandleInSession(const bool is_user_input, SharedFD hal_cli,
- const FsmInput fsm_input) {
- if (!is_user_input) {
- ConfUiLog(FATAL) << "cmd" << ToString(fsm_input)
- << "should not be handled in HandleInSession";
- ReportErrorToHal(hal_cli, "wrong hal command");
- return;
+bool Session::HandleInSession(SharedFD hal_cli, const FsmInput fsm_input,
+ const ConfUiMessage& conf_ui_msg) {
+ auto invalid_input_handler = [&, this]() {
+ ReportErrorToHal(hal_cli, HostError::kSystemError);
+ ConfUiLog(ERROR) << "cmd " << ToString(fsm_input)
+ << " should not be handled in HandleInSession";
+ };
+
+ if (!IsUserInput(fsm_input)) {
+ invalid_input_handler();
+ return false;
}
- // send to hal_cli either confirm or cancel
- if (fsm_input != FsmInput::kUserConfirm &&
- fsm_input != FsmInput::kUserCancel) {
- /*
- * TODO([email protected]): change here when other user inputs must
- * be handled
- *
- */
- if (!packet::SendAck(hal_cli, session_id_, true,
- "invalid user input error")) {
- // note that input is what we control in memory
- ConfUiCheck(false) << "Input must be either confirm or cancel for now.";
+ const auto& user_input_msg =
+ static_cast<const ConfUiSecureUserSelectionMessage&>(conf_ui_msg);
+ const auto response = user_input_msg.GetResponse();
+ if (response == UserResponse::kUnknown ||
+ response == UserResponse::kUserAbort) {
+ invalid_input_handler();
+ return false;
+ }
+ const bool is_secure_input = user_input_msg.IsSecure();
+
+ ConfUiLog(VERBOSE) << "In HandleInSession, session " << session_id_
+ << " is sending the user input " << ToString(fsm_input);
+
+ bool is_success = false;
+ if (response == UserResponse::kCancel) {
+ // no need to sign
+ is_success =
+ SendResponse(hal_cli, session_id_, UserResponse::kCancel,
+ std::vector<std::uint8_t>{}, std::vector<std::uint8_t>{});
+ } else {
+ message_ = std::move(cbor_->GetMessage());
+ auto message_opt = (is_secure_input ? Sign(message_) : TestSign(message_));
+ if (!message_opt) {
+ ReportErrorToHal(hal_cli, HostError::kSystemError);
+ return false;
}
- return;
+ signed_confirmation_ = message_opt.value();
+ is_success = SendResponse(hal_cli, session_id_, UserResponse::kConfirm,
+ signed_confirmation_, message_);
}
- ConfUiLog(DEBUG) << "In HandlieInSession, session" << session_id_
- << "is sending the user input" << ToString(fsm_input);
- auto selection = UserResponse::kConfirm;
- if (fsm_input == FsmInput::kUserCancel) {
- selection = UserResponse::kCancel;
- }
- if (!packet::SendResponse(hal_cli, session_id_, selection)) {
- ConfUiLog(FATAL) << "I/O error in sending user response to HAL";
+ if (!is_success) {
+ ConfUiLog(ERROR) << "I/O error in sending user response to HAL";
+ return false;
}
state_ = MainLoopState::kWaitStop;
- return;
+ return true;
}
-void Session::HandleWaitStop(const bool is_user_input, SharedFD hal_cli,
- const FsmInput fsm_input) {
- using namespace cuttlefish::confui::packet;
-
- if (is_user_input) {
+bool Session::HandleWaitStop(SharedFD hal_cli, const FsmInput fsm_input) {
+ if (IsUserInput(fsm_input)) {
// ignore user input
state_ = MainLoopState::kWaitStop;
- return;
+ return true;
}
if (fsm_input == FsmInput::kHalStop) {
- ConfUiLog(DEBUG) << "Handling Abort in kWaitStop.";
- Kill(hal_cli, "stopped");
- return;
+ ConfUiLog(VERBOSE) << "Handling Abort in kWaitStop.";
+ ScheduleToTerminate();
+ return true;
}
+ ReportErrorToHal(hal_cli, HostError::kSystemError);
ConfUiLog(FATAL) << "In WaitStop, received wrong HAL command "
<< ToString(fsm_input);
- state_ = MainLoopState::kAwaitCleanup;
- return;
+ return false;
}
} // end of namespace confui
diff --git a/host/libs/confui/session.h b/host/libs/confui/session.h
index 87e98c9..1afccac 100644
--- a/host/libs/confui/session.h
+++ b/host/libs/confui/session.h
@@ -16,13 +16,19 @@
#pragma once
+#include <atomic>
+#include <chrono>
#include <memory>
+#include <string>
+
+#include <teeui/msg_formatting.h>
#include "common/libs/confui/confui.h"
+#include "host/libs/confui/cbor.h"
#include "host/libs/confui/host_mode_ctrl.h"
#include "host/libs/confui/host_renderer.h"
#include "host/libs/confui/server_common.h"
-#include "host/libs/confui/session.h"
+#include "host/libs/confui/sign.h"
#include "host/libs/screen_connector/screen_connector.h"
namespace cuttlefish {
@@ -37,8 +43,8 @@
*/
class Session {
public:
- Session(const std::string& session_id, const std::uint32_t display_num,
- ConfUiRenderer& host_renderer, HostModeCtrl& host_mode_ctrl,
+ Session(const std::string& session_name, const std::uint32_t display_num,
+ HostModeCtrl& host_mode_ctrl,
ScreenConnectorFrameRenderer& screen_connector,
const std::string& locale = "en");
@@ -48,9 +54,8 @@
MainLoopState GetState() { return state_; }
- MainLoopState Transition(const bool is_user_input, SharedFD& hal_cli,
- const FsmInput fsm_input,
- const std::string& additional_info);
+ MainLoopState Transition(SharedFD& hal_cli, const FsmInput fsm_input,
+ const ConfUiMessage& conf_ui_message);
/**
* this make a transition from kWaitStop or kInSession to kSuspend
@@ -63,49 +68,82 @@
bool Restore(SharedFD hal_cli);
// abort session
- bool Abort(SharedFD hal_cli);
+ void Abort();
+
+ // client on the host wants to abort
+ // should let the guest know it
+ void UserAbort(SharedFD hal_cli);
bool IsSuspended() const;
void CleanUp();
+ bool IsConfirm(const int x, const int y) {
+ return renderer_->IsInConfirm(x, y);
+ }
+
+ bool IsCancel(const int x, const int y) {
+ return renderer_->IsInCancel(x, y);
+ }
+
+ // tell if grace period has passed
+ bool IsReadyForUserInput() const;
+
private:
- /** create a frame, and render it on the vnc/webRTC client
+ bool IsUserInput(const FsmInput fsm_input) {
+ return fsm_input == FsmInput::kUserEvent;
+ }
+
+ /** create a frame, and render it on the webRTC client
*
* note that this does not check host_ctrl_mode_
*/
- bool RenderDialog(const std::string& msg, const std::string& locale);
+ bool RenderDialog();
// transition actions on each state per input
// the new state will be save to the state_ at the end of each call
- void HandleInit(const bool is_user_input, SharedFD hal_cli,
- const FsmInput fsm_input, const std::string& additional_info);
+ //
+ // when false is returned, the FSM must terminate
+ // and, no need to let the guest know
+ bool HandleInit(SharedFD hal_cli, const FsmInput fsm_input,
+ const ConfUiMessage& conf_ui_msg);
- void HandleWaitStop(const bool is_user_input, SharedFD hal_cli,
- const FsmInput fsm_input);
+ bool HandleWaitStop(SharedFD hal_cli, const FsmInput fsm_input);
- void HandleInSession(const bool is_user_input, SharedFD hal_cli,
- const FsmInput fsm_input);
-
- bool Kill(SharedFD hal_cli, const std::string& response_msg);
+ bool HandleInSession(SharedFD hal_cli, const FsmInput fsm_input,
+ const ConfUiMessage& conf_ui_msg);
// report with an error ack to HAL, and reset the FSM
- void ReportErrorToHal(SharedFD hal_cli, const std::string& msg);
+ bool ReportErrorToHal(SharedFD hal_cli, const std::string& msg);
+
+ void ScheduleToTerminate();
+
+ bool IsInverted() const;
+ bool IsMagnified() const;
const std::string session_id_;
const std::uint32_t display_num_;
- // host renderer is shared across sessions
- ConfUiRenderer& renderer_;
+ std::unique_ptr<ConfUiRenderer> renderer_;
HostModeCtrl& host_mode_ctrl_;
ScreenConnectorFrameRenderer& screen_connector_;
// only context to save
- std::string prompt_;
+ std::string prompt_text_;
std::string locale_;
+ std::vector<teeui::UIOption> ui_options_;
+ std::vector<std::uint8_t> extra_data_;
+ // the second argument for resultCB of promptUserConfirmation
+ std::vector<std::uint8_t> signed_confirmation_;
+ std::vector<std::uint8_t> message_;
- // effectively, this variables are shared with vnc, webRTC thread
+ std::unique_ptr<Cbor> cbor_;
+
+ // effectively, this variables are shared with webRTC thread
// the input demuxer will check the confirmation UI mode based on this
std::atomic<MainLoopState> state_;
MainLoopState saved_state_; // for restore/suspend
+ using Clock = std::chrono::steady_clock;
+ using TimePoint = std::chrono::time_point<Clock>;
+ std::unique_ptr<TimePoint> start_time_;
};
} // end of namespace confui
} // end of namespace cuttlefish
diff --git a/host/libs/confui/sign.cc b/host/libs/confui/sign.cc
new file mode 100644
index 0000000..2c454e5
--- /dev/null
+++ b/host/libs/confui/sign.cc
@@ -0,0 +1,130 @@
+/*
+ * Copyright 2021, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "host/libs/confui/sign.h"
+
+#include <openssl/hmac.h>
+#include <openssl/sha.h>
+
+#include <string>
+
+#include <android-base/logging.h>
+
+#include "common/libs/confui/confui.h"
+#include "common/libs/fs/shared_fd.h"
+#include "common/libs/security/confui_sign.h"
+#include "host/commands/kernel_log_monitor/utils.h"
+#include "host/libs/config/cuttlefish_config.h"
+#include "host/libs/confui/sign_utils.h"
+
+namespace cuttlefish {
+namespace confui {
+namespace {
+std::string GetSecureEnvSocketPath() {
+ auto config = cuttlefish::CuttlefishConfig::Get();
+ CHECK(config) << "Config must not be null";
+ auto instance = config->ForDefaultInstance();
+ return instance.PerInstanceInternalPath("confui_sign.sock");
+}
+
+/**
+ * the secure_env signing server may be on slightly later than
+ * confirmation UI host/webRTC process.
+ */
+SharedFD ConnectToSecureEnv() {
+ auto socket_path = GetSecureEnvSocketPath();
+ SharedFD socket_to_secure_env =
+ SharedFD::SocketLocalClient(socket_path, false, SOCK_STREAM);
+ return socket_to_secure_env;
+}
+} // end of namespace
+
+class HMacImplementation {
+ public:
+ static std::optional<support::hmac_t> hmac256(
+ const support::auth_token_key_t& key,
+ std::initializer_list<support::ByteBufferProxy> buffers);
+};
+
+std::optional<support::hmac_t> HMacImplementation::hmac256(
+ const support::auth_token_key_t& key,
+ std::initializer_list<support::ByteBufferProxy> buffers) {
+ HMAC_CTX hmacCtx;
+ HMAC_CTX_init(&hmacCtx);
+ if (!HMAC_Init_ex(&hmacCtx, key.data(), key.size(), EVP_sha256(), nullptr)) {
+ return {};
+ }
+ for (auto& buffer : buffers) {
+ if (!HMAC_Update(&hmacCtx, buffer.data(), buffer.size())) {
+ return {};
+ }
+ }
+ support::hmac_t result;
+ if (!HMAC_Final(&hmacCtx, result.data(), nullptr)) {
+ return {};
+ }
+ return result;
+}
+
+/**
+ * The test key is 32byte word with all bytes set to TestKeyBits::BYTE.
+ */
+enum class TestKeyBits : uint8_t {
+ BYTE = 165 /* 0xA5 */,
+};
+
+std::optional<std::vector<std::uint8_t>> TestSign(
+ const std::vector<std::uint8_t>& message) {
+ // the same as userConfirm()
+ using namespace support;
+ auth_token_key_t key;
+ key.fill(static_cast<std::uint8_t>(TestKeyBits::BYTE));
+ using HMacer = HMacImplementation;
+ auto confirm_signed_opt =
+ HMacer::hmac256(key, {"confirmation token", message});
+ if (!confirm_signed_opt) {
+ return std::nullopt;
+ }
+ auto confirm_signed = confirm_signed_opt.value();
+ return {
+ std::vector<std::uint8_t>(confirm_signed.begin(), confirm_signed.end())};
+}
+
+std::optional<std::vector<std::uint8_t>> Sign(
+ const std::vector<std::uint8_t>& message) {
+ SharedFD socket_to_secure_env = ConnectToSecureEnv();
+ if (!socket_to_secure_env->IsOpen()) {
+ ConfUiLog(ERROR) << "Failed to connect to secure_env signing server.";
+ return std::nullopt;
+ }
+ ConfUiSignRequester sign_client(socket_to_secure_env);
+ // request signature
+ sign_client.Request(message);
+ auto response_opt = sign_client.Receive();
+ if (!response_opt) {
+ ConfUiLog(ERROR) << "Received nullopt";
+ return std::nullopt;
+ }
+ // respond should be either error code or the signature
+ auto response = std::move(response_opt.value());
+ if (response.error_ != SignMessageError::kOk) {
+ ConfUiLog(ERROR) << "Response was received with non-OK error code";
+ return std::nullopt;
+ }
+ return {response.payload_};
+}
+} // namespace confui
+} // end of namespace cuttlefish
diff --git a/host/libs/confui/sign.h b/host/libs/confui/sign.h
new file mode 100644
index 0000000..234df8d
--- /dev/null
+++ b/host/libs/confui/sign.h
@@ -0,0 +1,35 @@
+/*
+ * Copyright 2021, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <cstdint>
+#include <optional>
+#include <vector>
+
+namespace cuttlefish {
+namespace confui {
+
+// sign with the local test key
+std::optional<std::vector<std::uint8_t>> TestSign(
+ const std::vector<std::uint8_t>& message);
+
+// sign with secure_env
+std::optional<std::vector<std::uint8_t>> Sign(
+ const std::vector<std::uint8_t>& message);
+
+} // namespace confui
+} // end of namespace cuttlefish
diff --git a/host/libs/confui/sign_utils.h b/host/libs/confui/sign_utils.h
new file mode 100644
index 0000000..929f32a
--- /dev/null
+++ b/host/libs/confui/sign_utils.h
@@ -0,0 +1,126 @@
+/*
+ * Copyright 2021, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <array>
+#include <cstdint>
+
+namespace cuttlefish {
+namespace confui {
+namespace support {
+using auth_token_key_t = std::array<std::uint8_t, 32>;
+using hmac_t = auth_token_key_t;
+
+template <typename T>
+auto bytes_cast(const T& v) -> const uint8_t (&)[sizeof(T)] {
+ return *reinterpret_cast<const uint8_t(*)[sizeof(T)]>(&v);
+}
+template <typename T>
+auto bytes_cast(T& v) -> uint8_t (&)[sizeof(T)] {
+ return *reinterpret_cast<uint8_t(*)[sizeof(T)]>(&v);
+}
+
+template <typename IntType, uint32_t byteOrder>
+struct choose_hton;
+
+template <typename IntType>
+struct choose_hton<IntType, __ORDER_LITTLE_ENDIAN__> {
+ inline static IntType hton(const IntType& value) {
+ IntType result = {};
+ const unsigned char* inbytes =
+ reinterpret_cast<const unsigned char*>(&value);
+ unsigned char* outbytes = reinterpret_cast<unsigned char*>(&result);
+ for (int i = sizeof(IntType) - 1; i >= 0; --i) {
+ *(outbytes++) = inbytes[i];
+ }
+ return result;
+ }
+};
+
+template <typename IntType>
+struct choose_hton<IntType, __ORDER_BIG_ENDIAN__> {
+ inline static IntType hton(const IntType& value) { return value; }
+};
+
+template <typename IntType>
+inline IntType hton(const IntType& value) {
+ return choose_hton<IntType, __BYTE_ORDER__>::hton(value);
+}
+
+class ByteBufferProxy {
+ template <typename T>
+ struct has_data {
+ template <typename U>
+ static int f(const U*, const void*) {
+ return 0;
+ }
+ template <typename U>
+ static int* f(const U* u, decltype(u->data())) {
+ return nullptr;
+ }
+ static constexpr bool value =
+ std::is_pointer<decltype(f((T*)nullptr, ""))>::value;
+ };
+
+ public:
+ template <typename T>
+ ByteBufferProxy(const T& buffer, decltype(buffer.data()) = nullptr)
+ : data_(reinterpret_cast<const uint8_t*>(buffer.data())),
+ size_(buffer.size()) {
+ static_assert(sizeof(decltype(*buffer.data())) == 1, "elements to large");
+ }
+
+ // this overload kicks in for types that have .c_str() but not .data(), such
+ // as hidl_string. std::string has both so we need to explicitly disable this
+ // overload if .data() is present.
+ template <typename T>
+ ByteBufferProxy(
+ const T& buffer,
+ std::enable_if_t<!has_data<T>::value, decltype(buffer.c_str())> = nullptr)
+ : data_(reinterpret_cast<const uint8_t*>(buffer.c_str())),
+ size_(buffer.size()) {
+ static_assert(sizeof(decltype(*buffer.c_str())) == 1, "elements to large");
+ }
+
+ template <size_t size>
+ ByteBufferProxy(const char (&buffer)[size])
+ : data_(reinterpret_cast<const uint8_t*>(buffer)), size_(size - 1) {
+ static_assert(size > 0, "even an empty string must be 0-terminated");
+ }
+
+ template <size_t size>
+ ByteBufferProxy(const uint8_t (&buffer)[size]) : data_(buffer), size_(size) {}
+
+ ByteBufferProxy() : data_(nullptr), size_(0) {}
+
+ const uint8_t* data() const { return data_; }
+ size_t size() const { return size_; }
+
+ const uint8_t* begin() const { return data_; }
+ const uint8_t* end() const { return data_ + size_; }
+
+ private:
+ const uint8_t* data_;
+ size_t size_;
+};
+
+// copied from:
+// hardware/interface/confirmationui/support/include/android/hardware/confirmationui/support/confirmationui_utils.h
+
+} // end of namespace support
+} // end of namespace confui
+} // end of namespace cuttlefish
diff --git a/host/libs/graphics_detector/Android.bp b/host/libs/graphics_detector/Android.bp
index bd6a55e..679825a 100644
--- a/host/libs/graphics_detector/Android.bp
+++ b/host/libs/graphics_detector/Android.bp
@@ -36,6 +36,7 @@
],
header_libs: [
"egl_headers",
+ "gl_headers",
"vulkan_headers",
],
shared_libs: [
@@ -44,3 +45,19 @@
],
defaults: ["cuttlefish_host"],
}
+
+cc_binary {
+ name: "detect_graphics",
+ srcs: [
+ "detect_graphics.cpp",
+ ],
+ shared_libs: [
+ "libbase",
+ "liblog",
+ ],
+ static_libs: [
+ "libcuttlefish_graphics_detector",
+ "libgflags",
+ ],
+ defaults: ["cuttlefish_host"],
+}
diff --git a/host/libs/graphics_detector/detect_graphics.cpp b/host/libs/graphics_detector/detect_graphics.cpp
new file mode 100644
index 0000000..6032520
--- /dev/null
+++ b/host/libs/graphics_detector/detect_graphics.cpp
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <string>
+
+#include <gflags/gflags.h>
+
+#include "android-base/logging.h"
+#include "host/libs/graphics_detector/graphics_detector.h"
+
+int main(int argc, char* argv[]) {
+ ::android::base::InitLogging(argv, android::base::StdioLogger);
+ ::android::base::SetMinimumLogSeverity(android::base::VERBOSE);
+ ::gflags::ParseCommandLineFlags(&argc, &argv, true);
+ LOG(INFO) << cuttlefish::GetGraphicsAvailabilityWithSubprocessCheck();
+}
\ No newline at end of file
diff --git a/host/libs/graphics_detector/graphics_detector.cpp b/host/libs/graphics_detector/graphics_detector.cpp
index 367e637..5776c41 100644
--- a/host/libs/graphics_detector/graphics_detector.cpp
+++ b/host/libs/graphics_detector/graphics_detector.cpp
@@ -21,6 +21,7 @@
#include <EGL/egl.h>
#include <EGL/eglext.h>
+#include <GLES2/gl2.h>
#include <android-base/logging.h>
#include <android-base/strings.h>
#include <dlfcn.h>
@@ -57,50 +58,50 @@
void PopulateGlAvailability(GraphicsAvailability* availability) {
ManagedLibrary gl_lib(dlopen(kGlLib, RTLD_NOW | RTLD_LOCAL));
if (!gl_lib) {
- LOG(VERBOSE) << "Failed to dlopen " << kGlLib << ".";
+ LOG(DEBUG) << "Failed to dlopen " << kGlLib << ".";
return;
}
- LOG(VERBOSE) << "Loaded " << kGlLib << ".";
+ LOG(DEBUG) << "Loaded " << kGlLib << ".";
availability->has_gl = true;
}
void PopulateGles1Availability(GraphicsAvailability* availability) {
ManagedLibrary gles1_lib(dlopen(kGles1Lib, RTLD_NOW | RTLD_LOCAL));
if (!gles1_lib) {
- LOG(VERBOSE) << "Failed to dlopen " << kGles1Lib << ".";
+ LOG(DEBUG) << "Failed to dlopen " << kGles1Lib << ".";
return;
}
- LOG(VERBOSE) << "Loaded " << kGles1Lib << ".";
+ LOG(DEBUG) << "Loaded " << kGles1Lib << ".";
availability->has_gles1 = true;
}
void PopulateGles2Availability(GraphicsAvailability* availability) {
ManagedLibrary gles2_lib(dlopen(kGles2Lib, RTLD_NOW | RTLD_LOCAL));
if (!gles2_lib) {
- LOG(VERBOSE) << "Failed to dlopen " << kGles2Lib << ".";
+ LOG(DEBUG) << "Failed to dlopen " << kGles2Lib << ".";
return;
}
- LOG(VERBOSE) << "Loaded " << kGles2Lib << ".";
+ LOG(DEBUG) << "Loaded " << kGles2Lib << ".";
availability->has_gles2 = true;
}
void PopulateEglAvailability(GraphicsAvailability* availability) {
ManagedLibrary egllib(dlopen(kEglLib, RTLD_NOW | RTLD_LOCAL));
if (!egllib) {
- LOG(VERBOSE) << "Failed to dlopen " << kEglLib << ".";
+ LOG(DEBUG) << "Failed to dlopen " << kEglLib << ".";
return;
}
- LOG(VERBOSE) << "Loaded " << kEglLib << ".";
+ LOG(DEBUG) << "Loaded " << kEglLib << ".";
availability->has_egl = true;
PFNEGLGETPROCADDRESSPROC eglGetProcAddress =
reinterpret_cast<PFNEGLGETPROCADDRESSPROC>(
dlsym(egllib.get(), "eglGetProcAddress"));
if (eglGetProcAddress == nullptr) {
- LOG(VERBOSE) << "Failed to find function eglGetProcAddress.";
+ LOG(DEBUG) << "Failed to find function eglGetProcAddress.";
return;
}
- LOG(VERBOSE) << "Loaded eglGetProcAddress.";
+ LOG(DEBUG) << "Loaded eglGetProcAddress.";
// Some implementations have it so that eglGetProcAddress is only for
// loading EXT functions.
@@ -115,155 +116,127 @@
PFNEGLGETERRORPROC eglGetError =
reinterpret_cast<PFNEGLGETERRORPROC>(EglLoadFunction("eglGetError"));
if (eglGetError == nullptr) {
- LOG(VERBOSE) << "Failed to find function eglGetError.";
+ LOG(DEBUG) << "Failed to find function eglGetError.";
return;
}
- LOG(VERBOSE) << "Loaded eglGetError.";
+ LOG(DEBUG) << "Loaded eglGetError.";
PFNEGLGETDISPLAYPROC eglGetDisplay =
reinterpret_cast<PFNEGLGETDISPLAYPROC>(EglLoadFunction("eglGetDisplay"));
if (eglGetDisplay == nullptr) {
- LOG(VERBOSE) << "Failed to find function eglGetDisplay.";
+ LOG(DEBUG) << "Failed to find function eglGetDisplay.";
return;
}
- LOG(VERBOSE) << "Loaded eglGetDisplay.";
-
- EGLDisplay default_display = eglGetDisplay(EGL_DEFAULT_DISPLAY);
- if (default_display == EGL_NO_DISPLAY) {
- LOG(VERBOSE) << "Failed to get default display. " << eglGetError();
- return;
- }
- LOG(VERBOSE) << "Found default display.";
- availability->has_egl_default_display = true;
-
- PFNEGLINITIALIZEPROC eglInitialize =
- reinterpret_cast<PFNEGLINITIALIZEPROC>(EglLoadFunction("eglInitialize"));
- if (eglInitialize == nullptr) {
- LOG(VERBOSE) << "Failed to find function eglQueryString";
- return;
- }
-
- EGLint client_version_major = 0;
- EGLint client_version_minor = 0;
- if (eglInitialize(default_display,
- &client_version_major,
- &client_version_minor) != EGL_TRUE) {
- LOG(VERBOSE) << "Failed to initialize default display.";
- return;
- }
- LOG(VERBOSE) << "Initialized default display.";
+ LOG(DEBUG) << "Loaded eglGetDisplay.";
PFNEGLQUERYSTRINGPROC eglQueryString =
reinterpret_cast<PFNEGLQUERYSTRINGPROC>(EglLoadFunction("eglQueryString"));
if (eglQueryString == nullptr) {
- LOG(VERBOSE) << "Failed to find function eglQueryString";
+ LOG(DEBUG) << "Failed to find function eglQueryString";
return;
}
- LOG(VERBOSE) << "Loaded eglQueryString.";
+ LOG(DEBUG) << "Loaded eglQueryString.";
- std::string client_extensions;
- if (client_version_major >= 1 && client_version_minor >= 5) {
- client_extensions = eglQueryString(EGL_NO_DISPLAY, EGL_EXTENSIONS);
- }
- availability->egl_client_extensions = client_extensions;
-
- EGLDisplay display = EGL_NO_DISPLAY;
-
- if (client_extensions.find("EGL_EXT_platform_base") != std::string::npos) {
- LOG(VERBOSE) << "Client extension EGL_EXT_platform_base is supported.";
+ EGLDisplay display = eglGetDisplay(EGL_DEFAULT_DISPLAY);
+ if (display != EGL_NO_DISPLAY) {
+ LOG(DEBUG) << "Found default display.";
+ } else {
+ LOG(DEBUG) << "Failed to get default display. " << eglGetError()
+ << ". Attempting to get surfaceless display via "
+ << "eglGetPlatformDisplayEXT(EGL_PLATFORM_SURFACELESS_MESA)";
PFNEGLGETPLATFORMDISPLAYEXTPROC eglGetPlatformDisplayEXT =
reinterpret_cast<PFNEGLGETPLATFORMDISPLAYEXTPROC>(
EglLoadFunction("eglGetPlatformDisplayEXT"));
if (eglGetPlatformDisplayEXT == nullptr) {
- LOG(VERBOSE) << "Failed to find function eglGetPlatformDisplayEXT";
- return;
+ LOG(DEBUG) << "Failed to find function eglGetPlatformDisplayEXT";
+ } else {
+ display = eglGetPlatformDisplayEXT(EGL_PLATFORM_SURFACELESS_MESA,
+ EGL_DEFAULT_DISPLAY, NULL);
}
+ }
- display =
- eglGetPlatformDisplayEXT(EGL_PLATFORM_SURFACELESS_MESA,
- EGL_DEFAULT_DISPLAY,
- NULL);
- } else {
- LOG(VERBOSE) << "Failed to find client extension EGL_EXT_platform_base.";
- }
if (display == EGL_NO_DISPLAY) {
- LOG(VERBOSE) << "Failed to get EGL_PLATFORM_SURFACELESS_MESA display..."
- << "failing back to EGL_DEFAULT_DISPLAY display.";
- display = default_display;
- }
- if (display == EGL_NO_DISPLAY) {
- LOG(VERBOSE) << "Failed to find display.";
+ LOG(DEBUG) << "Failed to find display.";
return;
}
+ PFNEGLINITIALIZEPROC eglInitialize =
+ reinterpret_cast<PFNEGLINITIALIZEPROC>(EglLoadFunction("eglInitialize"));
+ if (eglInitialize == nullptr) {
+ LOG(DEBUG) << "Failed to find function eglQueryString";
+ return;
+ }
+
+ EGLint client_version_major = 0;
+ EGLint client_version_minor = 0;
if (eglInitialize(display,
&client_version_major,
&client_version_minor) != EGL_TRUE) {
- LOG(VERBOSE) << "Failed to initialize surfaceless display.";
+ LOG(DEBUG) << "Failed to initialize display.";
return;
}
- LOG(VERBOSE) << "Initialized surfaceless display.";
+ LOG(DEBUG) << "Initialized display.";
const std::string version_string = eglQueryString(display, EGL_VERSION);
if (version_string.empty()) {
- LOG(VERBOSE) << "Failed to query client version.";
+ LOG(DEBUG) << "Failed to query client version.";
return;
}
- LOG(VERBOSE) << "Found version: " << version_string;
+ LOG(DEBUG) << "Found version: " << version_string;
availability->egl_version = version_string;
const std::string vendor_string = eglQueryString(display, EGL_VENDOR);
if (vendor_string.empty()) {
- LOG(VERBOSE) << "Failed to query vendor.";
+ LOG(DEBUG) << "Failed to query vendor.";
return;
}
- LOG(VERBOSE) << "Found vendor: " << vendor_string;
+ LOG(DEBUG) << "Found vendor: " << vendor_string;
availability->egl_vendor = vendor_string;
const std::string extensions_string = eglQueryString(display, EGL_EXTENSIONS);
if (extensions_string.empty()) {
- LOG(VERBOSE) << "Failed to query extensions.";
+ LOG(DEBUG) << "Failed to query extensions.";
return;
}
- LOG(VERBOSE) << "Found extensions: " << extensions_string;
+ LOG(DEBUG) << "Found extensions: " << extensions_string;
availability->egl_extensions = extensions_string;
if (extensions_string.find(kSurfacelessContextExt) == std::string::npos) {
- LOG(VERBOSE) << "Failed to find extension EGL_KHR_surfaceless_context.";
+ LOG(DEBUG) << "Failed to find extension EGL_KHR_surfaceless_context.";
return;
}
const std::string display_apis_string = eglQueryString(display,
EGL_CLIENT_APIS);
if (display_apis_string.empty()) {
- LOG(VERBOSE) << "Failed to query display apis.";
+ LOG(DEBUG) << "Failed to query display apis.";
return;
}
- LOG(VERBOSE) << "Found display apis: " << display_apis_string;
+ LOG(DEBUG) << "Found display apis: " << display_apis_string;
PFNEGLBINDAPIPROC eglBindAPI =
reinterpret_cast<PFNEGLBINDAPIPROC>(EglLoadFunction("eglBindAPI"));
if (eglBindAPI == nullptr) {
- LOG(VERBOSE) << "Failed to find function eglBindAPI";
+ LOG(DEBUG) << "Failed to find function eglBindAPI";
return;
}
- LOG(VERBOSE) << "Loaded eglBindAPI.";
+ LOG(DEBUG) << "Loaded eglBindAPI.";
if (eglBindAPI(EGL_OPENGL_ES_API) == EGL_FALSE) {
- LOG(VERBOSE) << "Failed to bind GLES API.";
+ LOG(DEBUG) << "Failed to bind GLES API.";
return;
}
- LOG(VERBOSE) << "Bound GLES API.";
+ LOG(DEBUG) << "Bound GLES API.";
PFNEGLCHOOSECONFIGPROC eglChooseConfig =
reinterpret_cast<PFNEGLCHOOSECONFIGPROC>(
EglLoadFunction("eglChooseConfig"));
if (eglChooseConfig == nullptr) {
- LOG(VERBOSE) << "Failed to find function eglChooseConfig";
+ LOG(DEBUG) << "Failed to find function eglChooseConfig";
return;
}
- LOG(VERBOSE) << "Loaded eglChooseConfig.";
+ LOG(DEBUG) << "Loaded eglChooseConfig.";
const EGLint framebuffer_config_attributes[] = {
EGL_SURFACE_TYPE, EGL_PBUFFER_BIT,
@@ -282,28 +255,28 @@
&framebuffer_config,
1,
&num_framebuffer_configs) != EGL_TRUE) {
- LOG(VERBOSE) << "Failed to find matching framebuffer config.";
+ LOG(DEBUG) << "Failed to find matching framebuffer config.";
return;
}
- LOG(VERBOSE) << "Found matching framebuffer config.";
+ LOG(DEBUG) << "Found matching framebuffer config.";
PFNEGLCREATECONTEXTPROC eglCreateContext =
reinterpret_cast<PFNEGLCREATECONTEXTPROC>(
EglLoadFunction("eglCreateContext"));
if (eglCreateContext == nullptr) {
- LOG(VERBOSE) << "Failed to find function eglCreateContext";
+ LOG(DEBUG) << "Failed to find function eglCreateContext";
return;
}
- LOG(VERBOSE) << "Loaded eglCreateContext.";
+ LOG(DEBUG) << "Loaded eglCreateContext.";
PFNEGLDESTROYCONTEXTPROC eglDestroyContext =
reinterpret_cast<PFNEGLDESTROYCONTEXTPROC>(
EglLoadFunction("eglDestroyContext"));
if (eglDestroyContext == nullptr) {
- LOG(VERBOSE) << "Failed to find function eglDestroyContext";
+ LOG(DEBUG) << "Failed to find function eglDestroyContext";
return;
}
- LOG(VERBOSE) << "Loaded eglDestroyContext.";
+ LOG(DEBUG) << "Loaded eglDestroyContext.";
const EGLint context_attributes[] = {
EGL_CONTEXT_CLIENT_VERSION, 2,
@@ -315,38 +288,77 @@
EGL_NO_CONTEXT,
context_attributes);
if (context == EGL_NO_CONTEXT) {
- LOG(VERBOSE) << "Failed to create EGL context.";
+ LOG(DEBUG) << "Failed to create EGL context.";
return;
}
- LOG(VERBOSE) << "Created EGL context.";
+ LOG(DEBUG) << "Created EGL context.";
Closer context_closer([&]() { eglDestroyContext(display, context); });
PFNEGLMAKECURRENTPROC eglMakeCurrent =
reinterpret_cast<PFNEGLMAKECURRENTPROC>(EglLoadFunction("eglMakeCurrent"));
if (eglMakeCurrent == nullptr) {
- LOG(VERBOSE) << "Failed to find function eglMakeCurrent";
+ LOG(DEBUG) << "Failed to find function eglMakeCurrent";
return;
}
- LOG(VERBOSE) << "Loaded eglMakeCurrent.";
+ LOG(DEBUG) << "Loaded eglMakeCurrent.";
if (eglMakeCurrent(display,
EGL_NO_SURFACE,
EGL_NO_SURFACE,
context) != EGL_TRUE) {
- LOG(VERBOSE) << "Failed to make EGL context current.";
+ LOG(DEBUG) << "Failed to make EGL context current.";
return;
}
- LOG(VERBOSE) << "Make EGL context current.";
- availability->has_egl_surfaceless_with_gles = true;
+ LOG(DEBUG) << "Make EGL context current.";
+ availability->can_init_gles2_on_egl_surfaceless = true;
+
+ PFNGLGETSTRINGPROC glGetString =
+ reinterpret_cast<PFNGLGETSTRINGPROC>(eglGetProcAddress("glGetString"));
+
+ const GLubyte* gles2_vendor = glGetString(GL_VENDOR);
+ if (gles2_vendor == nullptr) {
+ LOG(DEBUG) << "Failed to query GLES2 vendor.";
+ return;
+ }
+ const std::string gles2_vendor_string((const char*)gles2_vendor);
+ LOG(DEBUG) << "Found GLES2 vendor: " << gles2_vendor_string;
+ availability->gles2_vendor = gles2_vendor_string;
+
+ const GLubyte* gles2_version = glGetString(GL_VERSION);
+ if (gles2_version == nullptr) {
+ LOG(DEBUG) << "Failed to query GLES2 vendor.";
+ return;
+ }
+ const std::string gles2_version_string((const char*)gles2_version);
+ LOG(DEBUG) << "Found GLES2 version: " << gles2_version_string;
+ availability->gles2_version = gles2_version_string;
+
+ const GLubyte* gles2_renderer = glGetString(GL_RENDERER);
+ if (gles2_renderer == nullptr) {
+ LOG(DEBUG) << "Failed to query GLES2 renderer.";
+ return;
+ }
+ const std::string gles2_renderer_string((const char*)gles2_renderer);
+ LOG(DEBUG) << "Found GLES2 renderer: " << gles2_renderer_string;
+ availability->gles2_renderer = gles2_renderer_string;
+
+ const GLubyte* gles2_extensions = glGetString(GL_EXTENSIONS);
+ if (gles2_extensions == nullptr) {
+ LOG(DEBUG) << "Failed to query GLES2 extensions.";
+ return;
+ }
+ const std::string gles2_extensions_string((const char*)gles2_extensions);
+ LOG(DEBUG) << "Found GLES2 extensions: " << gles2_extensions_string;
+ availability->gles2_extensions = gles2_extensions_string;
}
void PopulateVulkanAvailability(GraphicsAvailability* availability) {
ManagedLibrary vklib(dlopen(kVulkanLib, RTLD_NOW | RTLD_LOCAL));
if (!vklib) {
- LOG(VERBOSE) << "Failed to dlopen " << kVulkanLib << ".";
+ LOG(DEBUG) << "Failed to dlopen " << kVulkanLib << ".";
return;
}
- LOG(VERBOSE) << "Loaded " << kVulkanLib << ".";
+ LOG(DEBUG) << "Loaded " << kVulkanLib << ".";
availability->has_vulkan = true;
uint32_t instance_version = 0;
@@ -355,7 +367,7 @@
reinterpret_cast<PFN_vkGetInstanceProcAddr>(
dlsym(vklib.get(), "vkGetInstanceProcAddr"));
if (vkGetInstanceProcAddr == nullptr) {
- LOG(VERBOSE) << "Failed to find symbol vkGetInstanceProcAddr.";
+ LOG(DEBUG) << "Failed to find symbol vkGetInstanceProcAddr.";
return;
}
@@ -371,7 +383,7 @@
reinterpret_cast<PFN_vkCreateInstance>(
vkGetInstanceProcAddr(VK_NULL_HANDLE, "vkCreateInstance"));
if (vkCreateInstance == nullptr) {
- LOG(VERBOSE) << "Failed to get function vkCreateInstance.";
+ LOG(DEBUG) << "Failed to get function vkCreateInstance.";
return;
}
@@ -398,25 +410,25 @@
VkResult result = vkCreateInstance(&instance_create_info, nullptr, &instance);
if (result != VK_SUCCESS) {
if (result == VK_ERROR_OUT_OF_HOST_MEMORY) {
- LOG(VERBOSE) << "Failed to create Vulkan instance: "
+ LOG(DEBUG) << "Failed to create Vulkan instance: "
<< "VK_ERROR_OUT_OF_HOST_MEMORY.";
} else if (result == VK_ERROR_OUT_OF_DEVICE_MEMORY) {
- LOG(VERBOSE) << "Failed to create Vulkan instance: "
+ LOG(DEBUG) << "Failed to create Vulkan instance: "
<< "VK_ERROR_OUT_OF_DEVICE_MEMORY.";
} else if (result == VK_ERROR_INITIALIZATION_FAILED) {
- LOG(VERBOSE) << "Failed to create Vulkan instance: "
+ LOG(DEBUG) << "Failed to create Vulkan instance: "
<< "VK_ERROR_INITIALIZATION_FAILED.";
} else if (result == VK_ERROR_LAYER_NOT_PRESENT) {
- LOG(VERBOSE) << "Failed to create Vulkan instance: "
+ LOG(DEBUG) << "Failed to create Vulkan instance: "
<< "VK_ERROR_LAYER_NOT_PRESENT.";
} else if (result == VK_ERROR_EXTENSION_NOT_PRESENT) {
- LOG(VERBOSE) << "Failed to create Vulkan instance: "
+ LOG(DEBUG) << "Failed to create Vulkan instance: "
<< "VK_ERROR_EXTENSION_NOT_PRESENT.";
} else if (result == VK_ERROR_INCOMPATIBLE_DRIVER) {
- LOG(VERBOSE) << "Failed to create Vulkan instance: "
+ LOG(DEBUG) << "Failed to create Vulkan instance: "
<< "VK_ERROR_INCOMPATIBLE_DRIVER.";
} else {
- LOG(VERBOSE) << "Failed to create Vulkan instance.";
+ LOG(DEBUG) << "Failed to create Vulkan instance.";
}
return;
}
@@ -425,7 +437,7 @@
reinterpret_cast<PFN_vkDestroyInstance>(
vkGetInstanceProcAddr(instance, "vkDestroyInstance"));
if (vkDestroyInstance == nullptr) {
- LOG(VERBOSE) << "Failed to get function vkDestroyInstance.";
+ LOG(DEBUG) << "Failed to get function vkDestroyInstance.";
return;
}
@@ -435,7 +447,7 @@
reinterpret_cast<PFN_vkEnumeratePhysicalDevices>(
vkGetInstanceProcAddr(instance, "vkEnumeratePhysicalDevices"));
if (vkEnumeratePhysicalDevices == nullptr) {
- LOG(VERBOSE) << "Failed to "
+ LOG(DEBUG) << "Failed to "
<< "vkGetInstanceProcAddr(vkEnumeratePhysicalDevices).";
return;
}
@@ -444,7 +456,7 @@
reinterpret_cast<PFN_vkGetPhysicalDeviceProperties>(
vkGetInstanceProcAddr(instance, "vkGetPhysicalDeviceProperties"));
if (vkGetPhysicalDeviceProperties == nullptr) {
- LOG(VERBOSE) << "Failed to "
+ LOG(DEBUG) << "Failed to "
<< "vkGetInstanceProcAddr(vkGetPhysicalDeviceProperties).";
return;
}
@@ -453,7 +465,7 @@
reinterpret_cast<PFN_vkEnumerateDeviceExtensionProperties>(
vkGetInstanceProcAddr(instance, "vkEnumerateDeviceExtensionProperties"));
if (vkEnumerateDeviceExtensionProperties == nullptr) {
- LOG(VERBOSE) << "Failed to "
+ LOG(DEBUG) << "Failed to "
<< "vkGetInstanceProcAddr("
<< "vkEnumerateDeviceExtensionProperties"
<< ").";
@@ -464,32 +476,32 @@
result = vkEnumeratePhysicalDevices(instance, &device_count, nullptr);
if (result != VK_SUCCESS) {
if (result == VK_INCOMPLETE) {
- LOG(VERBOSE) << "Failed to enumerate physical device count: "
+ LOG(DEBUG) << "Failed to enumerate physical device count: "
<< "VK_INCOMPLETE";
} else if (result == VK_ERROR_OUT_OF_HOST_MEMORY) {
- LOG(VERBOSE) << "Failed to enumerate physical device count: "
+ LOG(DEBUG) << "Failed to enumerate physical device count: "
<< "VK_ERROR_OUT_OF_HOST_MEMORY";
} else if (result == VK_ERROR_OUT_OF_DEVICE_MEMORY) {
- LOG(VERBOSE) << "Failed to enumerate physical device count: "
+ LOG(DEBUG) << "Failed to enumerate physical device count: "
<< "VK_ERROR_OUT_OF_DEVICE_MEMORY";
} else if (result == VK_ERROR_INITIALIZATION_FAILED) {
- LOG(VERBOSE) << "Failed to enumerate physical device count: "
+ LOG(DEBUG) << "Failed to enumerate physical device count: "
<< "VK_ERROR_INITIALIZATION_FAILED";
} else {
- LOG(VERBOSE) << "Failed to enumerate physical device count.";
+ LOG(DEBUG) << "Failed to enumerate physical device count.";
}
return;
}
if (device_count == 0) {
- LOG(VERBOSE) << "No physical devices present.";
+ LOG(DEBUG) << "No physical devices present.";
return;
}
std::vector<VkPhysicalDevice> devices(device_count, VK_NULL_HANDLE);
result = vkEnumeratePhysicalDevices(instance, &device_count, devices.data());
if (result != VK_SUCCESS) {
- LOG(VERBOSE) << "Failed to enumerate physical devices.";
+ LOG(DEBUG) << "Failed to enumerate physical devices.";
return;
}
@@ -497,6 +509,8 @@
VkPhysicalDeviceProperties device_properties = {};
vkGetPhysicalDeviceProperties(device, &device_properties);
+ LOG(DEBUG) << "Found physical device: " << device_properties.deviceName;
+
uint32_t device_extensions_count = 0;
vkEnumerateDeviceExtensionProperties(device,
nullptr,
@@ -519,6 +533,9 @@
std::string device_extensions_string =
android::base::Join(device_extensions_strings, ' ');
+ LOG(DEBUG) << "Found physical device extensions: "
+ << device_extensions_string;
+
if (device_properties.deviceType == VK_PHYSICAL_DEVICE_TYPE_DISCRETE_GPU) {
availability->has_discrete_gpu = true;
availability->discrete_gpu_device_name = device_properties.deviceName;
@@ -528,6 +545,18 @@
}
}
+std::string ToLower(const std::string& v) {
+ std::string result = v;
+ std::transform(result.begin(), result.end(), result.begin(),
+ [](unsigned char c) { return std::tolower(c); });
+ return result;
+}
+
+bool IsLikelySoftwareRenderer(const std::string& renderer) {
+ const std::string lower_renderer = ToLower(renderer);
+ return lower_renderer.find("llvmpipe") != std::string::npos;
+}
+
GraphicsAvailability GetGraphicsAvailability() {
GraphicsAvailability availability;
@@ -544,7 +573,8 @@
bool ShouldEnableAcceleratedRendering(
const GraphicsAvailability& availability) {
- return availability.has_egl && availability.has_egl_surfaceless_with_gles &&
+ return availability.can_init_gles2_on_egl_surfaceless &&
+ !IsLikelySoftwareRenderer(availability.gles2_renderer) &&
availability.has_discrete_gpu;
}
@@ -560,13 +590,13 @@
}
int status;
if (waitpid(pid, &status, 0) != pid) {
- PLOG(ERROR) << "Failed to wait for graphics check subprocess";
+ PLOG(DEBUG) << "Failed to wait for graphics check subprocess";
return GraphicsAvailability{};
}
if (WIFEXITED(status) && WEXITSTATUS(status) == 0) {
return GetGraphicsAvailability();
}
- LOG(VERBOSE) << "Subprocess for detect_graphics failed with " << status;
+ LOG(DEBUG) << "Subprocess for detect_graphics failed with " << status;
return GraphicsAvailability{};
}
@@ -575,20 +605,32 @@
std::ios_base::fmtflags flags_backup(stream.flags());
stream << std::boolalpha;
stream << "Graphics Availability:\n";
- stream << "OpenGL available: " << availability.has_gl << "\n";
- stream << "OpenGL ES1 available: " << availability.has_gles1 << "\n";
- stream << "OpenGL ES2 available: " << availability.has_gles2 << "\n";
- stream << "EGL available: " << availability.has_egl << "\n";
+
+ stream << "\n";
+ stream << "OpenGL lib available: " << availability.has_gl << "\n";
+ stream << "OpenGL ES1 lib available: " << availability.has_gles1 << "\n";
+ stream << "OpenGL ES2 lib available: " << availability.has_gles2 << "\n";
+ stream << "EGL lib available: " << availability.has_egl << "\n";
+ stream << "Vulkan lib available: " << availability.has_vulkan << "\n";
+
+ stream << "\n";
stream << "EGL client extensions: " << availability.egl_client_extensions
<< "\n";
- stream << "EGL default display available: "
- << availability.has_egl_default_display << "\n";
+
+ stream << "\n";
stream << "EGL display vendor: " << availability.egl_vendor << "\n";
stream << "EGL display version: " << availability.egl_version << "\n";
stream << "EGL display extensions: " << availability.egl_extensions << "\n";
- stream << "EGL surfaceless display with GLES: "
- << availability.has_egl_surfaceless_with_gles << "\n";
- stream << "Vulkan available: " << availability.has_vulkan << "\n";
+
+ stream << "GLES2 can init on surfaceless display: "
+ << availability.can_init_gles2_on_egl_surfaceless << "\n";
+ stream << "\n";
+ stream << "GLES2 vendor: " << availability.gles2_vendor << "\n";
+ stream << "GLES2 version: " << availability.gles2_version << "\n";
+ stream << "GLES2 renderer: " << availability.gles2_renderer << "\n";
+ stream << "GLES2 extensions: " << availability.gles2_extensions << "\n";
+
+ stream << "\n";
stream << "Vulkan discrete GPU detected: " << availability.has_discrete_gpu
<< "\n";
if (availability.has_discrete_gpu) {
@@ -597,6 +639,11 @@
stream << "Vulkan discrete GPU device extensions: "
<< availability.discrete_gpu_device_extensions << "\n";
}
+
+ stream << "\n";
+ stream << "Accelerated rendering supported: "
+ << ShouldEnableAcceleratedRendering(availability);
+
stream.flags(flags_backup);
return stream;
}
diff --git a/host/libs/graphics_detector/graphics_detector.h b/host/libs/graphics_detector/graphics_detector.h
index 83a7068..1bacc3d 100644
--- a/host/libs/graphics_detector/graphics_detector.h
+++ b/host/libs/graphics_detector/graphics_detector.h
@@ -25,13 +25,20 @@
bool has_gles1 = false;
bool has_gles2 = false;
bool has_egl = false;
- bool has_egl_default_display = false;
+ bool has_vulkan = false;
+
std::string egl_client_extensions;
+
std::string egl_version;
std::string egl_vendor;
std::string egl_extensions;
- bool has_egl_surfaceless_with_gles = false;
- bool has_vulkan = false;
+
+ bool can_init_gles2_on_egl_surfaceless = false;
+ std::string gles2_vendor;
+ std::string gles2_version;
+ std::string gles2_renderer;
+ std::string gles2_extensions;
+
bool has_discrete_gpu = false;
std::string discrete_gpu_device_name;
std::string discrete_gpu_device_extensions;
diff --git a/host/libs/image_aggregator/image_aggregator.cc b/host/libs/image_aggregator/image_aggregator.cc
index b6b412d..49ec27e 100644
--- a/host/libs/image_aggregator/image_aggregator.cc
+++ b/host/libs/image_aggregator/image_aggregator.cc
@@ -31,6 +31,7 @@
#include <android-base/file.h>
#include <android-base/logging.h>
+#include <android-base/strings.h>
#include <cdisk_spec.pb.h>
#include <google/protobuf/text_format.h>
#include <sparse/sparse.h>
@@ -39,6 +40,7 @@
#include "common/libs/fs/shared_buf.h"
#include "common/libs/fs/shared_fd.h"
+#include "common/libs/utils/cf_endian.h"
#include "common/libs/utils/files.h"
#include "common/libs/utils/size_utils.h"
#include "common/libs/utils/subprocess.h"
@@ -48,6 +50,8 @@
namespace {
constexpr int GPT_NUM_PARTITIONS = 128;
+static const std::string CDISK_MAGIC = "composite_disk\x1d";
+static const std::string QCOW2_MAGIC = "QFI\xfb";
/**
* Creates a "Protective" MBR Partition Table header. The GUID
@@ -100,32 +104,53 @@
struct __attribute__((packed)) GptBeginning {
MasterBootRecord protective_mbr;
GptHeader header;
- std::uint8_t header_padding[420];
+ std::uint8_t header_padding[SECTOR_SIZE - sizeof(GptHeader)];
GptPartitionEntry entries[GPT_NUM_PARTITIONS];
std::uint8_t partition_alignment[3072];
};
-static_assert(sizeof(GptBeginning) == SECTOR_SIZE * 40);
+static_assert(AlignToPowerOf2(sizeof(GptBeginning), PARTITION_SIZE_SHIFT) ==
+ sizeof(GptBeginning));
struct __attribute__((packed)) GptEnd {
GptPartitionEntry entries[GPT_NUM_PARTITIONS];
GptHeader footer;
- std::uint8_t footer_padding[420];
+ std::uint8_t footer_padding[SECTOR_SIZE - sizeof(GptHeader)];
};
-static_assert(sizeof(GptEnd) == SECTOR_SIZE * 33);
+static_assert(sizeof(GptEnd) % SECTOR_SIZE == 0);
struct PartitionInfo {
MultipleImagePartition source;
- std::uint64_t guest_size;
- std::uint64_t host_size;
+ std::uint64_t size;
std::uint64_t offset;
+
+ std::uint64_t AlignedSize() const { return AlignToPartitionSize(size); }
};
+struct __attribute__((packed)) QCowHeader {
+ Be32 magic;
+ Be32 version;
+ Be64 backing_file_offset;
+ Be32 backing_file_size;
+ Be32 cluster_bits;
+ Be64 size;
+ Be32 crypt_method;
+ Be32 l1_size;
+ Be64 l1_table_offset;
+ Be64 refcount_table_offset;
+ Be32 refcount_table_clusters;
+ Be32 nb_snapshots;
+ Be64 snapshots_offset;
+};
+
+static_assert(sizeof(QCowHeader) == 72);
+
/*
- * Returns the file size of `file_path`. If `file_path` is an Android-Sparse
- * file, returns the file size it would have after being converted to a raw
- * file.
+ * Returns the expanded file size of `file_path`. Note that the raw size of
+ * files doesn't match how large they may appear inside a VM.
+ *
+ * Supported types: Composite disk image, Qcows2, Android-Sparse, Raw
*
* Android-Sparse is a file format invented by Android that optimizes for
* chunks of zeroes or repeated data. The Android build system can produce
@@ -133,15 +158,63 @@
* disk file, as the imag eflashing process also can handle Android-Sparse
* images.
*/
-std::uint64_t UnsparsedSize(const std::string& file_path) {
- auto fd = open(file_path.c_str(), O_RDONLY);
- CHECK(fd >= 0) << "Could not open \"" << file_path << "\""
- << strerror(errno);
- auto sparse = sparse_file_import(fd, /* verbose */ false, /* crc */ false);
- auto size =
- sparse ? sparse_file_len(sparse, false, true) : FileSize(file_path);
- close(fd);
- return size;
+std::uint64_t ExpandedStorageSize(const std::string& file_path) {
+ android::base::unique_fd fd(open(file_path.c_str(), O_RDONLY));
+ CHECK(fd.get() >= 0) << "Could not open \"" << file_path << "\""
+ << strerror(errno);
+
+ std::uint64_t file_size = FileSize(file_path);
+
+ // Try to read the disk in a nicely-aligned block size unless the whole file
+ // is smaller.
+ constexpr uint64_t MAGIC_BLOCK_SIZE = 4096;
+ std::string magic(std::min(file_size, MAGIC_BLOCK_SIZE), '\0');
+ if (!android::base::ReadFully(fd, magic.data(), magic.size())) {
+ PLOG(FATAL) << "Fail to read: " << file_path;
+ return 0;
+ }
+ CHECK(lseek(fd, 0, SEEK_SET) != -1)
+ << "Fail to seek(\"" << file_path << "\")" << strerror(errno);
+
+ // Composite disk image
+ if (android::base::StartsWith(magic, CDISK_MAGIC)) {
+ // seek to the beginning of proto message
+ CHECK(lseek(fd, CDISK_MAGIC.size(), SEEK_SET) != -1)
+ << "Fail to seek(\"" << file_path << "\")" << strerror(errno);
+ std::string message;
+ if (!android::base::ReadFdToString(fd, &message)) {
+ PLOG(FATAL) << "Fail to read(cdisk): " << file_path;
+ return 0;
+ }
+ CompositeDisk cdisk;
+ if (!cdisk.ParseFromString(message)) {
+ PLOG(FATAL) << "Fail to parse(cdisk): " << file_path;
+ return 0;
+ }
+ return cdisk.length();
+ }
+
+ // Qcow2 image
+ if (android::base::StartsWith(magic, QCOW2_MAGIC)) {
+ QCowHeader header;
+ if (!android::base::ReadFully(fd, &header, sizeof(QCowHeader))) {
+ PLOG(FATAL) << "Fail to read(qcow2 header): " << file_path;
+ return 0;
+ }
+ return header.size.as_uint64_t();
+ }
+
+ // Android-Sparse
+ if (auto sparse =
+ sparse_file_import(fd, /* verbose */ false, /* crc */ false);
+ sparse) {
+ auto size = sparse_file_len(sparse, false, true);
+ sparse_file_destroy(sparse);
+ return size;
+ }
+
+ // raw image file
+ return file_size;
}
/*
@@ -200,27 +273,24 @@
}
void AppendPartition(MultipleImagePartition source) {
- uint64_t host_size = 0;
+ uint64_t size = 0;
for (const auto& path : source.image_file_paths) {
- host_size += UnsparsedSize(path);
+ size += ExpandedStorageSize(path);
}
- auto guest_size = AlignToPowerOf2(host_size, PARTITION_SIZE_SHIFT);
- CHECK(host_size == guest_size || source.read_only)
+ auto aligned_size = AlignToPartitionSize(size);
+ CHECK(size == aligned_size || source.read_only)
<< "read-write partition " << source.label
<< " is not aligned to the size of " << (1 << PARTITION_SIZE_SHIFT);
partitions_.push_back(PartitionInfo{
.source = source,
- .guest_size = guest_size,
- .host_size = host_size,
+ .size = size,
.offset = next_disk_offset_,
});
- next_disk_offset_ =
- AlignToPowerOf2(next_disk_offset_ + guest_size, PARTITION_SIZE_SHIFT);
+ next_disk_offset_ = next_disk_offset_ + aligned_size;
}
std::uint64_t DiskSize() const {
- std::uint64_t val = next_disk_offset_ + sizeof(GptEnd);
- return AlignToPowerOf2(val, DISK_SIZE_SHIFT);
+ return AlignToPowerOf2(next_disk_offset_ + sizeof(GptEnd), DISK_SIZE_SHIFT);
}
/**
@@ -231,41 +301,41 @@
CompositeDisk MakeCompositeDiskSpec(const std::string& header_file,
const std::string& footer_file) const {
CompositeDisk disk;
- disk.set_version(1);
+ disk.set_version(2);
disk.set_length(DiskSize());
ComponentDisk* header = disk.add_component_disks();
- header->set_file_path(AbsolutePath(header_file));
+ header->set_file_path(header_file);
header->set_offset(0);
for (auto& partition : partitions_) {
- uint64_t host_size = 0;
+ uint64_t size = 0;
for (const auto& path : partition.source.image_file_paths) {
ComponentDisk* component = disk.add_component_disks();
- component->set_file_path(AbsolutePath(path));
- component->set_offset(partition.offset + host_size);
+ component->set_file_path(path);
+ component->set_offset(partition.offset + size);
component->set_read_write_capability(
partition.source.read_only ? ReadWriteCapability::READ_ONLY
: ReadWriteCapability::READ_WRITE);
- host_size += UnsparsedSize(path);
+ size += ExpandedStorageSize(path);
}
- CHECK(partition.host_size == host_size);
- // When partition's size differs from its size on the host
+ CHECK(partition.size == size);
+ // When partition's aligned size differs from its (unaligned) size
// reading the disk within the guest os would fail due to the gap.
// Putting any disk bigger than 4K can fill this gap.
// Here we reuse the header which is always > 4K.
// We don't fill the "writable" disk's hole and it should be an error
// because writes in the guest of can't be reflected to the backing file.
- if (partition.guest_size != partition.host_size) {
+ if (partition.AlignedSize() != partition.size) {
ComponentDisk* component = disk.add_component_disks();
- component->set_file_path(AbsolutePath(header_file));
- component->set_offset(partition.offset + partition.host_size);
+ component->set_file_path(header_file);
+ component->set_offset(partition.offset + partition.size);
component->set_read_write_capability(ReadWriteCapability::READ_ONLY);
}
}
ComponentDisk* footer = disk.add_component_disks();
- footer->set_file_path(AbsolutePath(footer_file));
+ footer->set_file_path(footer_file);
footer->set_offset(next_disk_offset_);
return disk;
@@ -284,19 +354,20 @@
return {};
}
GptBeginning gpt = {
- .protective_mbr = ProtectiveMbr(DiskSize()),
- .header = {
- .signature = {'E', 'F', 'I', ' ', 'P', 'A', 'R', 'T'},
- .revision = {0, 0, 1, 0},
- .header_size = sizeof(GptHeader),
- .current_lba = 1,
- .backup_lba = (next_disk_offset_ + sizeof(GptEnd)) / SECTOR_SIZE - 1,
- .first_usable_lba = sizeof(GptBeginning) / SECTOR_SIZE,
- .last_usable_lba = (next_disk_offset_ - SECTOR_SIZE) / SECTOR_SIZE,
- .partition_entries_lba = 2,
- .num_partition_entries = GPT_NUM_PARTITIONS,
- .partition_entry_size = sizeof(GptPartitionEntry),
- },
+ .protective_mbr = ProtectiveMbr(DiskSize()),
+ .header =
+ {
+ .signature = {'E', 'F', 'I', ' ', 'P', 'A', 'R', 'T'},
+ .revision = {0, 0, 1, 0},
+ .header_size = sizeof(GptHeader),
+ .current_lba = 1,
+ .backup_lba = (DiskSize() / SECTOR_SIZE) - 1,
+ .first_usable_lba = sizeof(GptBeginning) / SECTOR_SIZE,
+ .last_usable_lba = (next_disk_offset_ / SECTOR_SIZE) - 1,
+ .partition_entries_lba = 2,
+ .num_partition_entries = GPT_NUM_PARTITIONS,
+ .partition_entry_size = sizeof(GptPartitionEntry),
+ },
};
uuid_generate(gpt.header.disk_guid);
for (std::size_t i = 0; i < partitions_.size(); i++) {
@@ -304,7 +375,7 @@
gpt.entries[i] = GptPartitionEntry{
.first_lba = partition.offset / SECTOR_SIZE,
.last_lba =
- (partition.offset + partition.guest_size) / SECTOR_SIZE - 1,
+ (partition.offset + partition.AlignedSize()) / SECTOR_SIZE - 1,
};
uuid_generate(gpt.entries[i].unique_partition_guid);
if (uuid_parse(GetPartitionGUID(partition.source),
@@ -330,9 +401,10 @@
*/
GptEnd End(const GptBeginning& head) const {
GptEnd gpt;
- std::memcpy((void*) gpt.entries, (void*) head.entries, 128 * 128);
+ std::memcpy((void*)gpt.entries, (void*)head.entries, sizeof(gpt.entries));
gpt.footer = head.header;
- gpt.footer.partition_entries_lba = next_disk_offset_ / SECTOR_SIZE;
+ gpt.footer.partition_entries_lba =
+ (DiskSize() - sizeof(gpt.entries)) / SECTOR_SIZE - 1;
std::swap(gpt.footer.current_lba, gpt.footer.backup_lba);
gpt.footer.header_crc32 = 0;
gpt.footer.header_crc32 =
@@ -350,11 +422,17 @@
return true;
}
-bool WriteEnd(SharedFD out, const GptEnd& end, std::int64_t padding) {
- std::string end_str((const char*) &end, sizeof(GptEnd));
- end_str.resize(end_str.size() + padding, '\0');
- if (WriteAll(out, end_str) != end_str.size()) {
- LOG(ERROR) << "Could not write GPT end: " << out->StrError();
+bool WriteEnd(SharedFD out, const GptEnd& end) {
+ auto disk_size = (end.footer.current_lba + 1) * SECTOR_SIZE;
+ auto footer_start = (end.footer.last_usable_lba + 1) * SECTOR_SIZE;
+ auto padding = disk_size - footer_start - sizeof(GptEnd);
+ std::string padding_str(padding, '\0');
+ if (WriteAll(out, padding_str) != padding_str.size()) {
+ LOG(ERROR) << "Could not write GPT end padding: " << out->StrError();
+ return false;
+ }
+ if (WriteAllBinary(out, &end) != sizeof(end)) {
+ LOG(ERROR) << "Could not write GPT end contents: " << out->StrError();
return false;
}
return true;
@@ -435,8 +513,7 @@
<< "\" to \"" << output_path << "\": " << output->StrError();
}
// Handle disk images that are not aligned to PARTITION_SIZE_SHIFT
- std::uint64_t padding =
- AlignToPowerOf2(file_size, PARTITION_SIZE_SHIFT) - file_size;
+ std::uint64_t padding = AlignToPartitionSize(file_size) - file_size;
std::string padding_str;
padding_str.resize(padding, '\0');
if (WriteAll(output, padding_str) != padding_str.size()) {
@@ -444,9 +521,7 @@
<< "\": " << output->StrError();
}
}
- std::uint64_t padding =
- builder.DiskSize() - ((beginning.header.backup_lba + 1) * SECTOR_SIZE);
- if (!WriteEnd(output, builder.End(beginning), padding)) {
+ if (!WriteEnd(output, builder.End(beginning))) {
LOG(FATAL) << "Could not write GPT end to \"" << output_path
<< "\": " << output->StrError();
}
@@ -479,16 +554,14 @@
<< "\": " << header->StrError();
}
auto footer = SharedFD::Creat(footer_file, 0600);
- std::uint64_t padding =
- builder.DiskSize() - ((beginning.header.backup_lba + 1) * SECTOR_SIZE);
- if (!WriteEnd(footer, builder.End(beginning), padding)) {
+ if (!WriteEnd(footer, builder.End(beginning))) {
LOG(FATAL) << "Could not write GPT end to \"" << footer_file
<< "\": " << footer->StrError();
}
auto composite_proto = builder.MakeCompositeDiskSpec(header_file, footer_file);
std::ofstream composite(output_composite_path.c_str(),
std::ios::binary | std::ios::trunc);
- composite << "composite_disk\x1d";
+ composite << CDISK_MAGIC;
composite_proto.SerializeToOstream(&composite);
composite.flush();
}
@@ -496,13 +569,23 @@
void CreateQcowOverlay(const std::string& crosvm_path,
const std::string& backing_file,
const std::string& output_overlay_path) {
- Command crosvm_qcow2_cmd(crosvm_path);
- crosvm_qcow2_cmd.AddParameter("create_qcow2");
- crosvm_qcow2_cmd.AddParameter("--backing_file=", backing_file);
- crosvm_qcow2_cmd.AddParameter(output_overlay_path);
- int success = crosvm_qcow2_cmd.Start().Wait();
+ Command cmd(crosvm_path);
+ cmd.AddParameter("create_qcow2");
+ cmd.AddParameter("--backing_file=", backing_file);
+ cmd.AddParameter(output_overlay_path);
+
+ std::string stdout_str;
+ std::string stderr_str;
+ int success =
+ RunWithManagedStdio(std::move(cmd), nullptr, &stdout_str, &stderr_str);
+
if (success != 0) {
- LOG(FATAL) << "Unable to run crosvm create_qcow2. Exited with status " << success;
+ LOG(ERROR) << "Failed to run `" << crosvm_path
+ << " create_qcow2 --backing_file=" << backing_file << " "
+ << output_overlay_path << "`";
+ LOG(ERROR) << "stdout:\n###\n" << stdout_str << "\n###";
+ LOG(ERROR) << "stderr:\n###\n" << stderr_str << "\n###";
+ LOG(FATAL) << "Return code: \"" << success << "\"";
}
}
diff --git a/host/libs/screen_connector/Android.bp b/host/libs/screen_connector/Android.bp
index d4e4002..ae1ef66 100644
--- a/host/libs/screen_connector/Android.bp
+++ b/host/libs/screen_connector/Android.bp
@@ -41,5 +41,5 @@
"libteeui",
"libteeui_localization",
],
- defaults: ["cuttlefish_host"],
+ defaults: ["cuttlefish_buildhost_only"],
}
diff --git a/host/libs/screen_connector/screen_connector.h b/host/libs/screen_connector/screen_connector.h
index ca6b155..49e5253 100644
--- a/host/libs/screen_connector/screen_connector.h
+++ b/host/libs/screen_connector/screen_connector.h
@@ -16,8 +16,6 @@
#pragma once
-#include <cassert>
-#include <chrono>
#include <cstdint>
#include <functional>
#include <memory>
@@ -28,15 +26,15 @@
#include <type_traits>
#include <android-base/logging.h>
-#include "common/libs/concurrency/semaphore.h"
+
#include "common/libs/confui/confui.h"
#include "common/libs/fs/shared_fd.h"
#include "common/libs/utils/size_utils.h"
-
#include "host/libs/config/cuttlefish_config.h"
#include "host/libs/confui/host_mode_ctrl.h"
#include "host/libs/confui/host_utils.h"
#include "host/libs/screen_connector/screen_connector_common.h"
+#include "host/libs/screen_connector/screen_connector_multiplexer.h"
#include "host/libs/screen_connector/screen_connector_queue.h"
#include "host/libs/screen_connector/wayland_screen_connector.h"
@@ -51,17 +49,15 @@
static_assert(std::is_base_of<ScreenConnectorFrameInfo, ProcessedFrameType>::value,
"ProcessedFrameType should inherit ScreenConnectorFrameInfo");
+ using FrameMultiplexer = ScreenConnectorInputMultiplexer<ProcessedFrameType>;
+
/**
- * This is the type of the callback function WebRTC/VNC is supposed to provide
+ * This is the type of the callback function WebRTC is supposed to provide
* ScreenConnector with.
*
- * The callback function should be defined so that the two parameters are
- * given by the callback function caller (e.g. ScreenConnectorSource) and used
- * to fill out the ProcessedFrameType object, msg.
+ * The callback function is how a raw bytes frame should be processed for
+ * WebRTC
*
- * The ProcessedFrameType object is internally created by ScreenConnector,
- * filled out by the ScreenConnectorSource, and returned via OnNextFrame()
- * call.
*/
using GenerateProcessedFrameCallback = std::function<void(
std::uint32_t /*display_number*/, std::uint32_t /*frame_width*/,
@@ -88,9 +84,9 @@
virtual ~ScreenConnector() = default;
/**
- * set the callback function to be eventually used by Wayland/Socket-Based Connectors
+ * set the callback function to be eventually used by Wayland-Based
+ * Connector
*
- * @param[in] To tell how ScreenConnectorSource caches the frame & meta info
*/
void SetCallback(GenerateProcessedFrameCallback&& frame_callback) {
std::lock_guard<std::mutex> lock(streamer_callback_mutex_);
@@ -115,7 +111,7 @@
processed_frame);
}
- sc_android_queue_.PushBack(std::move(processed_frame));
+ sc_frame_multiplexer_.PushToAndroidQueue(std::move(processed_frame));
});
}
@@ -131,44 +127,7 @@
*
* NOTE THAT THIS IS THE ONLY CONSUMER OF THE TWO QUEUES
*/
- ProcessedFrameType OnNextFrame() {
- on_next_frame_cnt_++;
- while (true) {
- ConfUiLog(VERBOSE) << "Streamer waiting Semaphore with host ctrl mode ="
- << static_cast<std::uint32_t>(
- host_mode_ctrl_.GetMode())
- << " and cnd = #" << on_next_frame_cnt_;
- sc_sem_.SemWait();
- ConfUiLog(VERBOSE)
- << "Streamer got Semaphore'ed resources with host ctrl mode ="
- << static_cast<std::uint32_t>(host_mode_ctrl_.GetMode())
- << "and cnd = #" << on_next_frame_cnt_;
- // do something
- if (!sc_android_queue_.Empty()) {
- auto mode = host_mode_ctrl_.GetMode();
- if (mode == HostModeCtrl::ModeType::kAndroidMode) {
- ConfUiLog(VERBOSE)
- << "Streamer gets Android frame with host ctrl mode ="
- << static_cast<std::uint32_t>(mode) << "and cnd = #"
- << on_next_frame_cnt_;
- return sc_android_queue_.PopFront();
- }
- // AndroidFrameFetchingLoop could have added 1 or 2 frames
- // before it becomes Conf UI mode.
- ConfUiLog(VERBOSE)
- << "Streamer ignores Android frame with host ctrl mode ="
- << static_cast<std::uint32_t>(mode) << "and cnd = #"
- << on_next_frame_cnt_;
- sc_android_queue_.PopFront();
- continue;
- }
- ConfUiLog(VERBOSE) << "Streamer gets Conf UI frame with host ctrl mode = "
- << static_cast<std::uint32_t>(
- host_mode_ctrl_.GetMode())
- << " and cnd = #" << on_next_frame_cnt_;
- return sc_confui_queue_.PopFront();
- }
- }
+ ProcessedFrameType OnNextFrame() { return sc_frame_multiplexer_.Pop(); }
/**
* ConfUi calls this when it has frames to render
@@ -197,39 +156,32 @@
callback_from_streamer_(display_number, frame_width, frame_height,
frame_stride_bytes, frame_bytes, processed_frame);
// now add processed_frame to the queue
- sc_confui_queue_.PushBack(std::move(processed_frame));
+ sc_frame_multiplexer_.PushToConfUiQueue(std::move(processed_frame));
return true;
}
- // Let the screen connector know when there are clients connected
- void ReportClientsConnected(bool have_clients) {
- // screen connector implementation must implement ReportClientsConnected
- sc_android_src_->ReportClientsConnected(have_clients);
- return ;
- }
-
protected:
- template <typename T,
- typename = std::enable_if_t<
- std::is_base_of<ScreenConnectorSource, T>::value, void>>
- ScreenConnector(std::unique_ptr<T>&& impl, HostModeCtrl& host_mode_ctrl)
+ ScreenConnector(std::unique_ptr<WaylandScreenConnector>&& impl,
+ HostModeCtrl& host_mode_ctrl)
: sc_android_src_{std::move(impl)},
host_mode_ctrl_{host_mode_ctrl},
on_next_frame_cnt_{0},
render_confui_cnt_{0},
- sc_android_queue_{sc_sem_},
- sc_confui_queue_{sc_sem_} {}
+ sc_frame_multiplexer_{host_mode_ctrl_} {}
ScreenConnector() = delete;
private:
- // either socket_based or wayland
- std::unique_ptr<ScreenConnectorSource> sc_android_src_;
+ std::unique_ptr<WaylandScreenConnector> sc_android_src_;
HostModeCtrl& host_mode_ctrl_;
unsigned long long int on_next_frame_cnt_;
unsigned long long int render_confui_cnt_;
- Semaphore sc_sem_;
- ScreenConnectorQueue<ProcessedFrameType> sc_android_queue_;
- ScreenConnectorQueue<ProcessedFrameType> sc_confui_queue_;
+ /**
+ * internally has conf ui & android queues.
+ *
+ * multiplexting the two input queues, so the consumer gets one input
+ * at a time from the right queue
+ */
+ FrameMultiplexer sc_frame_multiplexer_;
GenerateProcessedFrameCallback callback_from_streamer_;
std::mutex streamer_callback_mutex_; // mutex to set & read callback_from_streamer_
std::condition_variable streamer_callback_set_cv_;
diff --git a/host/libs/screen_connector/screen_connector_common.h b/host/libs/screen_connector/screen_connector_common.h
index 1df6d86..32bac76 100644
--- a/host/libs/screen_connector/screen_connector_common.h
+++ b/host/libs/screen_connector/screen_connector_common.h
@@ -18,9 +18,9 @@
#include <cstdint>
#include <functional>
-#include <type_traits>
#include <android-base/logging.h>
+
#include "common/libs/utils/size_utils.h"
#include "host/libs/config/cuttlefish_config.h"
@@ -41,17 +41,6 @@
std::uint32_t /*frame_stride_bytes*/, //
std::uint8_t* /*frame_pixels*/)>;
-class ScreenConnectorSource {
- public:
- virtual ~ScreenConnectorSource() = default;
- // Runs the given callback on the next available frame after the given
- // frame number and returns true if successful.
- virtual void SetFrameCallback(
- GenerateProcessedFrameCallbackImpl frame_callback) = 0;
- virtual void ReportClientsConnected(bool /*have_clients*/) { /* ignore by default */ }
- ScreenConnectorSource() = default;
-};
-
struct ScreenConnectorInfo {
// functions are intended to be inlined
static constexpr std::uint32_t BytesPerPixel() { return 4; }
@@ -72,12 +61,21 @@
CHECK_GE(display_configs.size(), display_number);
return display_configs[display_number].width;
}
- static std::uint32_t ScreenStrideBytes(std::uint32_t display_number) {
- return AlignToPowerOf2(ScreenWidth(display_number) * BytesPerPixel(), 4);
+ static std::uint32_t ComputeScreenStrideBytes(const std::uint32_t w) {
+ return AlignToPowerOf2(w * BytesPerPixel(), 4);
}
- static std::uint32_t ScreenSizeInBytes(std::uint32_t display_number) {
- return ScreenStrideBytes(display_number) * ScreenHeight(display_number);
+ static std::uint32_t ComputeScreenSizeInBytes(const std::uint32_t w,
+ const std::uint32_t h) {
+ return ComputeScreenStrideBytes(w) * h;
}
+ static std::uint32_t ScreenStrideBytes(const std::uint32_t display_number) {
+ return ComputeScreenStrideBytes(ScreenWidth(display_number));
+ }
+ static std::uint32_t ScreenSizeInBytes(const std::uint32_t display_number) {
+ return ComputeScreenStrideBytes(ScreenWidth(display_number)) *
+ ScreenHeight(display_number);
+ }
+
private:
static auto ChkAndGetConfig() -> decltype(cuttlefish::CuttlefishConfig::Get()) {
auto config = cuttlefish::CuttlefishConfig::Get();
diff --git a/host/libs/screen_connector/screen_connector_multiplexer.h b/host/libs/screen_connector/screen_connector_multiplexer.h
new file mode 100644
index 0000000..b620531
--- /dev/null
+++ b/host/libs/screen_connector/screen_connector_multiplexer.h
@@ -0,0 +1,104 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <cstdint>
+
+#include "common/libs/concurrency/multiplexer.h"
+#include "common/libs/confui/confui.h"
+
+#include "host/libs/confui/host_mode_ctrl.h"
+#include "host/libs/screen_connector/screen_connector_queue.h"
+
+namespace cuttlefish {
+template <typename ProcessedFrameType>
+class ScreenConnectorInputMultiplexer {
+ using Queue = ScreenConnectorQueue<ProcessedFrameType>;
+ using Multiplexer = Multiplexer<ProcessedFrameType, Queue>;
+
+ public:
+ ScreenConnectorInputMultiplexer(HostModeCtrl& host_mode_ctrl)
+ : host_mode_ctrl_(host_mode_ctrl) {
+ sc_android_queue_id_ =
+ multiplexer_.RegisterQueue(multiplexer_.CreateQueue(/* q size */ 2));
+ sc_confui_queue_id_ =
+ multiplexer_.RegisterQueue(multiplexer_.CreateQueue(/* q size */ 2));
+ }
+
+ virtual ~ScreenConnectorInputMultiplexer() = default;
+
+ void PushToAndroidQueue(ProcessedFrameType&& t) {
+ multiplexer_.Push(sc_android_queue_id_, std::move(t));
+ }
+
+ void PushToConfUiQueue(ProcessedFrameType&& t) {
+ multiplexer_.Push(sc_confui_queue_id_, std::move(t));
+ }
+
+ // customize Pop()
+ ProcessedFrameType Pop() {
+ on_next_frame_cnt_++;
+
+ // is_discard_frame is thread-specific
+ bool is_discard_frame = false;
+
+ // callback to select the queue index, and update is_discard_frame
+ auto selector = [this, &is_discard_frame]() -> int {
+ if (multiplexer_.IsEmpty(sc_android_queue_id_)) {
+ ConfUiLog(VERBOSE)
+ << "Streamer gets Conf UI frame with host ctrl mode = "
+ << static_cast<std::uint32_t>(host_mode_ctrl_.GetMode())
+ << " and cnd = #" << on_next_frame_cnt_;
+ return sc_confui_queue_id_;
+ }
+ auto mode = host_mode_ctrl_.GetMode();
+ if (mode != HostModeCtrl::ModeType::kAndroidMode) {
+ // AndroidFrameFetchingLoop could have added 1 or 2 frames
+ // before it becomes Conf UI mode.
+ ConfUiLog(VERBOSE)
+ << "Streamer ignores Android frame with host ctrl mode ="
+ << static_cast<std::uint32_t>(mode) << "and cnd = #"
+ << on_next_frame_cnt_;
+ is_discard_frame = true;
+ }
+ ConfUiLog(VERBOSE) << "Streamer gets Android frame with host ctrl mode ="
+ << static_cast<std::uint32_t>(mode) << "and cnd = #"
+ << on_next_frame_cnt_;
+ return sc_android_queue_id_;
+ };
+
+ while (true) {
+ ConfUiLog(VERBOSE) << "Streamer waiting Semaphore with host ctrl mode ="
+ << static_cast<std::uint32_t>(
+ host_mode_ctrl_.GetMode())
+ << " and cnd = #" << on_next_frame_cnt_;
+ auto processed_frame = multiplexer_.Pop(selector);
+ if (!is_discard_frame) {
+ return processed_frame;
+ }
+ is_discard_frame = false;
+ }
+ }
+
+ private:
+ HostModeCtrl& host_mode_ctrl_;
+ Multiplexer multiplexer_;
+ unsigned long long int on_next_frame_cnt_;
+ int sc_android_queue_id_;
+ int sc_confui_queue_id_;
+};
+} // end of namespace cuttlefish
diff --git a/host/libs/screen_connector/screen_connector_queue.h b/host/libs/screen_connector/screen_connector_queue.h
index 2019168..66fd7f7 100644
--- a/host/libs/screen_connector/screen_connector_queue.h
+++ b/host/libs/screen_connector/screen_connector_queue.h
@@ -16,12 +16,11 @@
#pragma once
+#include <condition_variable>
#include <deque>
#include <memory>
-#include <thread>
#include <mutex>
-#include <condition_variable>
-#include <chrono>
+#include <thread>
#include "common/libs/concurrency/semaphore.h"
@@ -31,19 +30,17 @@
class ScreenConnectorQueue {
public:
- static const int kQSize = 2;
-
static_assert( is_movable<T>::value,
"Items in ScreenConnectorQueue should be std::mov-able");
- ScreenConnectorQueue(Semaphore& sc_sem)
- : q_mutex_(std::make_unique<std::mutex>()), sc_semaphore_(sc_sem) {}
+ ScreenConnectorQueue(const int q_max_size = 2)
+ : q_mutex_(std::make_unique<std::mutex>()), q_max_size_{q_max_size} {}
ScreenConnectorQueue(ScreenConnectorQueue&& cq) = delete;
ScreenConnectorQueue(const ScreenConnectorQueue& cq) = delete;
ScreenConnectorQueue& operator=(const ScreenConnectorQueue& cq) = delete;
ScreenConnectorQueue& operator=(ScreenConnectorQueue&& cq) = delete;
- bool Empty() const {
+ bool IsEmpty() const {
const std::lock_guard<std::mutex> lock(*q_mutex_);
return buffer_.empty();
}
@@ -60,23 +57,23 @@
}
/*
- * PushBack( std::move(src) );
+ * Push( std::move(src) );
*
- * Note: this queue is suppoed to be used only by ScreenConnector-
+ * Note: this queue is supposed to be used only by ScreenConnector-
* related components such as ScreenConnectorSource
*
- * The traditional assumption was that when webRTC or VNC calls
+ * The traditional assumption was that when webRTC calls
* OnFrameAfter, the call should be block until it could return
* one frame.
*
* Thus, the producers of this queue must not produce frames
- * much faster than the consumer, VNC or WebRTC consumes.
+ * much faster than the consumer, WebRTC consumes.
* Therefore, when the small buffer is full -- which means
- * VNC or WebRTC would not call OnFrameAfter --, the producer
+ * WebRTC would not call OnNextFrame --, the producer
* should stop adding itmes to the queue.
*
*/
- void PushBack(T&& item) {
+ void Push(T&& item) {
std::unique_lock<std::mutex> lock(*q_mutex_);
if (Full()) {
auto is_empty =
@@ -84,23 +81,11 @@
q_empty_.wait(lock, is_empty);
}
buffer_.push_back(std::move(item));
- /* Whether the total number of items in ALL queus is 0 or not
- * is tracked via a semaphore shared by all queues
- *
- * This is NOT intended to block queue from pushing an item
- * This IS intended to awake the screen_connector consumer thread
- * when one or more items are available at least in one queue
- */
- sc_semaphore_.SemPost();
}
- void PushBack(T& item) = delete;
- void PushBack(const T& item) = delete;
+ void Push(T& item) = delete;
+ void Push(const T& item) = delete;
- /*
- * PopFront must be preceded by sc_semaphore_.SemWaitItem()
- *
- */
- T PopFront() {
+ T Pop() {
const std::lock_guard<std::mutex> lock(*q_mutex_);
auto item = std::move(buffer_.front());
buffer_.pop_front();
@@ -114,12 +99,12 @@
bool Full() const {
// call this in a critical section
// after acquiring q_mutex_
- return kQSize == buffer_.size();
+ return q_max_size_ == buffer_.size();
}
std::deque<T> buffer_;
std::unique_ptr<std::mutex> q_mutex_;
std::condition_variable q_empty_;
- Semaphore& sc_semaphore_;
+ const int q_max_size_;
};
} // namespace cuttlefish
diff --git a/host/libs/screen_connector/wayland_screen_connector.h b/host/libs/screen_connector/wayland_screen_connector.h
index 36ed322..ab3120b 100644
--- a/host/libs/screen_connector/wayland_screen_connector.h
+++ b/host/libs/screen_connector/wayland_screen_connector.h
@@ -16,23 +16,19 @@
#pragma once
-#include "host/libs/screen_connector/screen_connector_common.h"
-
#include <memory>
+#include "host/libs/screen_connector/screen_connector_common.h"
#include "host/libs/wayland/wayland_server.h"
namespace cuttlefish {
-class WaylandScreenConnector : public ScreenConnectorSource {
+class WaylandScreenConnector {
public:
WaylandScreenConnector(int frames_fd);
-
- void SetFrameCallback(
- GenerateProcessedFrameCallbackImpl frame_callback) override;
+ void SetFrameCallback(GenerateProcessedFrameCallbackImpl frame_callback);
private:
std::unique_ptr<wayland::WaylandServer> server_;
};
-
}
diff --git a/host/libs/vm_manager/Android.bp b/host/libs/vm_manager/Android.bp
index 20c9027..18bcb52 100644
--- a/host/libs/vm_manager/Android.bp
+++ b/host/libs/vm_manager/Android.bp
@@ -17,10 +17,34 @@
default_applicable_licenses: ["Android-Apache-2.0"],
}
+soong_config_module_type {
+ name: "cf_cc_defaults",
+ module_type: "cc_defaults",
+ config_namespace: "cvdhost",
+ bool_variables: ["enforce_mac80211_hwsim"],
+ properties: ["cflags"],
+}
+
+// This is the customization layer driven by soong config variables.
+cf_cc_defaults {
+ name: "cvd_cc_defaults",
+ soong_config_variables: {
+ // PRODUCT_ENFORCE_MAC80211_HWSIM sets this
+ enforce_mac80211_hwsim: {
+ cflags: ["-DENFORCE_MAC80211_HWSIM=true"],
+ conditions_default: {
+ cflags: [],
+ }
+ },
+ }
+}
+
cc_library_static {
name: "libcuttlefish_vm_manager",
srcs: [
+ "crosvm_builder.cpp",
"crosvm_manager.cpp",
+ "gem5_manager.cpp",
"host_configuration.cpp",
"qemu_manager.cpp",
"vm_manager.cpp",
@@ -32,10 +56,15 @@
"libcuttlefish_fs",
"libcuttlefish_utils",
"libbase",
+ "libfruit",
"libjsoncpp",
],
static_libs: [
"libcuttlefish_host_config",
],
- defaults: ["cuttlefish_host", "cuttlefish_libicuuc"],
+ defaults: [
+ "cuttlefish_host",
+ "cuttlefish_libicuuc",
+ "cvd_cc_defaults",
+ ],
}
diff --git a/host/libs/vm_manager/crosvm_builder.cpp b/host/libs/vm_manager/crosvm_builder.cpp
new file mode 100644
index 0000000..cb7fbc4
--- /dev/null
+++ b/host/libs/vm_manager/crosvm_builder.cpp
@@ -0,0 +1,106 @@
+//
+// Copyright (C) 2021 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "host/libs/vm_manager/crosvm_builder.h"
+
+#include <android-base/logging.h>
+
+#include <string>
+
+#include "common/libs/utils/network.h"
+#include "common/libs/utils/subprocess.h"
+
+namespace cuttlefish {
+
+CrosvmBuilder::CrosvmBuilder() : command_("crosvm") {
+ command_.AddParameter("run");
+}
+
+void CrosvmBuilder::SetBinary(const std::string& binary) {
+ command_.SetExecutable(binary);
+}
+
+void CrosvmBuilder::AddControlSocket(const std::string& control_socket) {
+ // Store this value so it persists after std::move(this->Cmd())
+ auto crosvm = command_.Executable();
+ command_.SetStopper([crosvm, control_socket](Subprocess* proc) {
+ Command stop_cmd(crosvm);
+ stop_cmd.AddParameter("stop");
+ stop_cmd.AddParameter(control_socket);
+ if (stop_cmd.Start().Wait() == 0) {
+ return StopperResult::kStopSuccess;
+ }
+ LOG(WARNING) << "Failed to stop VMM nicely, attempting to KILL";
+ return KillSubprocess(proc) == StopperResult::kStopSuccess
+ ? StopperResult::kStopCrash
+ : StopperResult::kStopFailure;
+ });
+ command_.AddParameter("--socket=", control_socket);
+}
+
+void CrosvmBuilder::AddHvcSink() {
+ command_.AddParameter("--serial=hardware=virtio-console,num=", ++hvc_num_,
+ ",type=sink");
+}
+void CrosvmBuilder::AddHvcConsoleReadOnly(const std::string& output) {
+ command_.AddParameter("--serial=hardware=virtio-console,num=", ++hvc_num_,
+ ",type=file,path=", output, ",console=true");
+}
+void CrosvmBuilder::AddHvcReadOnly(const std::string& output) {
+ command_.AddParameter("--serial=hardware=virtio-console,num=", ++hvc_num_,
+ ",type=file,path=", output);
+}
+void CrosvmBuilder::AddHvcReadWrite(const std::string& output,
+ const std::string& input) {
+ command_.AddParameter("--serial=hardware=virtio-console,num=", ++hvc_num_,
+ ",type=file,path=", output, ",input=", input);
+}
+
+void CrosvmBuilder::AddSerialSink() {
+ command_.AddParameter("--serial=hardware=serial,num=", ++serial_num_,
+ ",type=sink");
+}
+void CrosvmBuilder::AddSerialConsoleReadOnly(const std::string& output) {
+ command_.AddParameter("--serial=hardware=serial,num=", ++serial_num_,
+ ",type=file,path=", output, ",earlycon=true");
+}
+void CrosvmBuilder::AddSerialConsoleReadWrite(const std::string& output,
+ const std::string& input) {
+ command_.AddParameter("--serial=hardware=serial,num=", ++serial_num_,
+ ",type=file,path=", output, ",input=", input,
+ ",earlycon=true");
+}
+void CrosvmBuilder::AddSerial(const std::string& output,
+ const std::string& input) {
+ command_.AddParameter("--serial=hardware=serial,num=", ++serial_num_,
+ ",type=file,path=", output, ",input=", input);
+}
+
+SharedFD CrosvmBuilder::AddTap(const std::string& tap_name) {
+ auto tap_fd = OpenTapInterface(tap_name);
+ if (tap_fd->IsOpen()) {
+ command_.AddParameter("--tap-fd=", tap_fd);
+ } else {
+ LOG(ERROR) << "Unable to connect to \"" << tap_name
+ << "\": " << tap_fd->StrError();
+ }
+ return tap_fd;
+}
+
+int CrosvmBuilder::HvcNum() { return hvc_num_; }
+
+Command& CrosvmBuilder::Cmd() { return command_; }
+
+} // namespace cuttlefish
diff --git a/host/libs/vm_manager/crosvm_builder.h b/host/libs/vm_manager/crosvm_builder.h
new file mode 100644
index 0000000..90457b2
--- /dev/null
+++ b/host/libs/vm_manager/crosvm_builder.h
@@ -0,0 +1,57 @@
+//
+// Copyright (C) 2021 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#pragma once
+
+#include <string>
+#include <utility>
+
+#include "common/libs/fs/shared_fd.h"
+#include "common/libs/utils/subprocess.h"
+
+namespace cuttlefish {
+
+class CrosvmBuilder {
+ public:
+ CrosvmBuilder();
+
+ void SetBinary(const std::string&);
+ void AddControlSocket(const std::string&);
+
+ void AddHvcSink();
+ void AddHvcConsoleReadOnly(const std::string& output);
+ void AddHvcReadOnly(const std::string& output);
+ void AddHvcReadWrite(const std::string& output, const std::string& input);
+
+ void AddSerialSink();
+ void AddSerialConsoleReadOnly(const std::string& output);
+ void AddSerialConsoleReadWrite(const std::string& output,
+ const std::string& input);
+ // [[deprecated("do not add any more users")]]
+ void AddSerial(const std::string& output, const std::string& input);
+
+ SharedFD AddTap(const std::string& tap_name);
+
+ int HvcNum();
+
+ Command& Cmd();
+
+ private:
+ Command command_;
+ int hvc_num_;
+ int serial_num_;
+};
+
+} // namespace cuttlefish
diff --git a/host/libs/vm_manager/crosvm_manager.cpp b/host/libs/vm_manager/crosvm_manager.cpp
index e424fd5..bf95250 100644
--- a/host/libs/vm_manager/crosvm_manager.cpp
+++ b/host/libs/vm_manager/crosvm_manager.cpp
@@ -16,23 +16,24 @@
#include "host/libs/vm_manager/crosvm_manager.h"
+#include <android-base/file.h>
+#include <android-base/logging.h>
+#include <android-base/strings.h>
#include <sys/stat.h>
#include <sys/types.h>
+#include <vulkan/vulkan.h>
#include <cassert>
#include <string>
#include <vector>
-#include <android-base/strings.h>
-#include <android-base/logging.h>
-#include <vulkan/vulkan.h>
-
#include "common/libs/utils/environment.h"
+#include "common/libs/utils/files.h"
#include "common/libs/utils/network.h"
#include "common/libs/utils/subprocess.h"
-#include "common/libs/utils/files.h"
#include "host/libs/config/cuttlefish_config.h"
#include "host/libs/config/known_paths.h"
+#include "host/libs/vm_manager/crosvm_builder.h"
#include "host/libs/vm_manager/qemu_manager.h"
namespace cuttlefish {
@@ -40,52 +41,10 @@
namespace {
-std::string GetControlSocketPath(const CuttlefishConfig& config) {
- return config.ForDefaultInstance()
- .PerInstanceInternalPath("crosvm_control.sock");
-}
-
-SharedFD AddTapFdParameter(Command* crosvm_cmd,
- const std::string& tap_name) {
- auto tap_fd = OpenTapInterface(tap_name);
- if (tap_fd->IsOpen()) {
- crosvm_cmd->AddParameter("--tap-fd=", tap_fd);
- } else {
- LOG(ERROR) << "Unable to connect to " << tap_name << ": "
- << tap_fd->StrError();
- }
- return tap_fd;
-}
-
-bool ReleaseDhcpLeases(const std::string& lease_path, SharedFD tap_fd) {
- auto lease_file_fd = SharedFD::Open(lease_path, O_RDONLY);
- if (!lease_file_fd->IsOpen()) {
- LOG(ERROR) << "Could not open leases file \"" << lease_path << '"';
- return false;
- }
- bool success = true;
- auto dhcp_leases = ParseDnsmasqLeases(lease_file_fd);
- for (auto& lease : dhcp_leases) {
- std::uint8_t dhcp_server_ip[] = {192, 168, 96, (std::uint8_t) (ForCurrentInstance(1) * 4 - 3)};
- if (!ReleaseDhcp4(tap_fd, lease.mac_address, lease.ip_address, dhcp_server_ip)) {
- LOG(ERROR) << "Failed to release " << lease;
- success = false;
- } else {
- LOG(INFO) << "Successfully dropped " << lease;
- }
- }
- return success;
-}
-
-bool Stop() {
- auto config = CuttlefishConfig::Get();
- Command command(config->crosvm_binary());
- command.AddParameter("stop");
- command.AddParameter(GetControlSocketPath(*config));
-
- auto process = command.Start();
-
- return process.Wait() == 0;
+std::string GetControlSocketPath(
+ const CuttlefishConfig::InstanceSpecific& instance,
+ const std::string& socket_name) {
+ return instance.PerInstanceInternalPath(socket_name.c_str());
}
} // namespace
@@ -98,39 +57,39 @@
#endif
}
-std::vector<std::string> CrosvmManager::ConfigureGpuMode(
- const std::string& gpu_mode) {
+std::vector<std::string> CrosvmManager::ConfigureGraphics(
+ const CuttlefishConfig& config) {
// Override the default HAL search paths in all cases. We do this because
// the HAL search path allows for fallbacks, and fallbacks in conjunction
// with properities lead to non-deterministic behavior while loading the
// HALs.
- if (gpu_mode == kGpuModeGuestSwiftshader) {
+ if (config.gpu_mode() == kGpuModeGuestSwiftshader) {
return {
"androidboot.cpuvulkan.version=" + std::to_string(VK_API_VERSION_1_2),
"androidboot.hardware.gralloc=minigbm",
- "androidboot.hardware.hwcomposer=ranchu",
+ "androidboot.hardware.hwcomposer="+ config.hwcomposer(),
"androidboot.hardware.egl=angle",
"androidboot.hardware.vulkan=pastel",
- };
+ "androidboot.opengles.version=196609"}; // OpenGL ES 3.1
}
- if (gpu_mode == kGpuModeDrmVirgl) {
+ if (config.gpu_mode() == kGpuModeDrmVirgl) {
return {
"androidboot.cpuvulkan.version=0",
"androidboot.hardware.gralloc=minigbm",
- "androidboot.hardware.hwcomposer=drm_minigbm",
+ "androidboot.hardware.hwcomposer=drm",
"androidboot.hardware.egl=mesa",
};
}
- if (gpu_mode == kGpuModeGfxStream) {
- return {
- "androidboot.cpuvulkan.version=0",
- "androidboot.hardware.gralloc=minigbm",
- "androidboot.hardware.hwcomposer=ranchu",
- "androidboot.hardware.egl=emulation",
- "androidboot.hardware.vulkan=ranchu",
- "androidboot.hardware.gltransport=virtio-gpu-asg",
- };
+ if (config.gpu_mode() == kGpuModeGfxStream) {
+ std::string gles_impl = config.enable_gpu_angle() ? "angle" : "emulation";
+ return {"androidboot.cpuvulkan.version=0",
+ "androidboot.hardware.gralloc=minigbm",
+ "androidboot.hardware.hwcomposer=" + config.hwcomposer(),
+ "androidboot.hardware.egl=" + gles_impl,
+ "androidboot.hardware.vulkan=ranchu",
+ "androidboot.hardware.gltransport=virtio-gpu-asg",
+ "androidboot.opengles.version=196608"}; // OpenGL ES 3.0
}
return {};
}
@@ -139,7 +98,8 @@
// TODO There is no way to control this assignment with crosvm (yet)
if (HostArch() == Arch::X86_64) {
// crosvm has an additional PCI device for an ISA bridge
- return ConfigureMultipleBootDevices("pci0000:00/0000:00:", 1, num_disks);
+ // virtio_gpu and virtio_wl precedes the first console or disk
+ return ConfigureMultipleBootDevices("pci0000:00/0000:00:", 3, num_disks);
} else {
// On ARM64 crosvm, block devices are on their own bridge, so we don't
// need to calculate it, and the path is always the same
@@ -147,110 +107,75 @@
}
}
+constexpr auto crosvm_socket = "crosvm_control.sock";
+
std::vector<Command> CrosvmManager::StartCommands(
const CuttlefishConfig& config) {
auto instance = config.ForDefaultInstance();
- Command crosvm_cmd(config.crosvm_binary(), [](Subprocess* proc) {
- auto stopped = Stop();
- if (stopped) {
- return true;
- }
- LOG(WARNING) << "Failed to stop VMM nicely, attempting to KILL";
- return KillSubprocess(proc);
- });
-
- int hvc_num = 0;
- int serial_num = 0;
- auto add_hvc_sink = [&crosvm_cmd, &hvc_num]() {
- crosvm_cmd.AddParameter("--serial=hardware=virtio-console,num=", ++hvc_num,
- ",type=sink");
- };
- auto add_serial_sink = [&crosvm_cmd, &serial_num]() {
- crosvm_cmd.AddParameter("--serial=hardware=serial,num=", ++serial_num,
- ",type=sink");
- };
- auto add_hvc_console = [&crosvm_cmd, &hvc_num](const std::string& output) {
- crosvm_cmd.AddParameter("--serial=hardware=virtio-console,num=", ++hvc_num,
- ",type=file,path=", output, ",console=true");
- };
- auto add_serial_console_ro = [&crosvm_cmd,
- &serial_num](const std::string& output) {
- crosvm_cmd.AddParameter("--serial=hardware=serial,num=", ++serial_num,
- ",type=file,path=", output, ",earlycon=true");
- };
- auto add_serial_console = [&crosvm_cmd, &serial_num](
- const std::string& output,
- const std::string& input) {
- crosvm_cmd.AddParameter("--serial=hardware=serial,num=", ++serial_num,
- ",type=file,path=", output, ",input=", input,
- ",earlycon=true");
- };
- auto add_hvc_ro = [&crosvm_cmd, &hvc_num](const std::string& output) {
- crosvm_cmd.AddParameter("--serial=hardware=virtio-console,num=", ++hvc_num,
- ",type=file,path=", output);
- };
- auto add_hvc = [&crosvm_cmd, &hvc_num](const std::string& output,
- const std::string& input) {
- crosvm_cmd.AddParameter("--serial=hardware=virtio-console,num=", ++hvc_num,
- ",type=file,path=", output, ",input=", input);
- };
- // Deprecated; do not add any more users
- auto add_serial = [&crosvm_cmd, &serial_num](const std::string& output,
- const std::string& input) {
- crosvm_cmd.AddParameter("--serial=hardware=serial,num=", ++serial_num,
- ",type=file,path=", output, ",input=", input);
- };
-
- crosvm_cmd.AddParameter("run");
+ CrosvmBuilder crosvm_cmd;
+ crosvm_cmd.SetBinary(config.crosvm_binary());
+ crosvm_cmd.AddControlSocket(GetControlSocketPath(instance, crosvm_socket));
if (!config.smt()) {
- crosvm_cmd.AddParameter("--no-smt");
+ crosvm_cmd.Cmd().AddParameter("--no-smt");
}
if (config.vhost_net()) {
- crosvm_cmd.AddParameter("--vhost-net");
+ crosvm_cmd.Cmd().AddParameter("--vhost-net");
}
+#ifdef ENFORCE_MAC80211_HWSIM
+ if (!config.vhost_user_mac80211_hwsim().empty()) {
+ crosvm_cmd.Cmd().AddParameter("--vhost-user-mac80211-hwsim=",
+ config.vhost_user_mac80211_hwsim());
+ }
+#endif
+
if (config.protected_vm()) {
- crosvm_cmd.AddParameter("--protected-vm");
+ crosvm_cmd.Cmd().AddParameter("--protected-vm");
}
if (config.gdb_port() > 0) {
CHECK(config.cpus() == 1) << "CPUs must be 1 for crosvm gdb mode";
- crosvm_cmd.AddParameter("--gdb=", config.gdb_port());
+ crosvm_cmd.Cmd().AddParameter("--gdb=", config.gdb_port());
}
+ auto gpu_capture_enabled = !config.gpu_capture_binary().empty();
auto gpu_mode = config.gpu_mode();
+ auto udmabuf_string = config.enable_gpu_udmabuf() ? "true" : "false";
+ auto angle_string = config.enable_gpu_angle() ? ",angle=true" : "";
if (gpu_mode == kGpuModeGuestSwiftshader) {
- crosvm_cmd.AddParameter("--gpu=2D");
+ crosvm_cmd.Cmd().AddParameter("--gpu=2D,udmabuf=", udmabuf_string);
} else if (gpu_mode == kGpuModeDrmVirgl || gpu_mode == kGpuModeGfxStream) {
- crosvm_cmd.AddParameter(gpu_mode == kGpuModeGfxStream ?
- "--gpu=gfxstream," : "--gpu=",
- "egl=true,surfaceless=true,glx=false,gles=true");
+ crosvm_cmd.Cmd().AddParameter(
+ gpu_mode == kGpuModeGfxStream ? "--gpu=gfxstream," : "--gpu=",
+ "egl=true,surfaceless=true,glx=false,gles=true,udmabuf=", udmabuf_string,
+ angle_string);
}
for (const auto& display_config : config.display_configs()) {
- crosvm_cmd.AddParameter("--gpu-display=", "width=", display_config.width,
- ",", "height=", display_config.height);
+ crosvm_cmd.Cmd().AddParameter(
+ "--gpu-display=", "width=", display_config.width, ",",
+ "height=", display_config.height);
}
- crosvm_cmd.AddParameter("--wayland-sock=", instance.frames_socket_path());
+ crosvm_cmd.Cmd().AddParameter("--wayland-sock=",
+ instance.frames_socket_path());
- // crosvm_cmd.AddParameter("--null-audio");
- crosvm_cmd.AddParameter("--mem=", config.memory_mb());
- crosvm_cmd.AddParameter("--cpus=", config.cpus());
+ // crosvm_cmd.Cmd().AddParameter("--null-audio");
+ crosvm_cmd.Cmd().AddParameter("--mem=", config.memory_mb());
+ crosvm_cmd.Cmd().AddParameter("--cpus=", config.cpus());
auto disk_num = instance.virtual_disk_paths().size();
CHECK_GE(VmManager::kMaxDisks, disk_num)
<< "Provided too many disks (" << disk_num << "), maximum "
<< VmManager::kMaxDisks << "supported";
for (const auto& disk : instance.virtual_disk_paths()) {
- crosvm_cmd.AddParameter(config.protected_vm() ? "--disk=" :
- "--rwdisk=", disk);
+ crosvm_cmd.Cmd().AddParameter(
+ config.protected_vm() ? "--disk=" : "--rwdisk=", disk);
}
- crosvm_cmd.AddParameter("--socket=", GetControlSocketPath(config));
- if (config.enable_vnc_server() || config.enable_webrtc()) {
+ if (config.enable_webrtc()) {
auto touch_type_parameter =
config.enable_webrtc() ? "--multi-touch=" : "--single-touch=";
@@ -260,28 +185,45 @@
for (int i = 0; i < display_configs.size(); ++i) {
auto display_config = display_configs[i];
- crosvm_cmd.AddParameter(touch_type_parameter,
- instance.touch_socket_path(i), ":",
- display_config.width, ":", display_config.height);
+ crosvm_cmd.Cmd().AddParameter(
+ touch_type_parameter, instance.touch_socket_path(i), ":",
+ display_config.width, ":", display_config.height);
}
- crosvm_cmd.AddParameter("--keyboard=", instance.keyboard_socket_path());
+ crosvm_cmd.Cmd().AddParameter("--keyboard=",
+ instance.keyboard_socket_path());
}
if (config.enable_webrtc()) {
- crosvm_cmd.AddParameter("--switches=", instance.switches_socket_path());
+ crosvm_cmd.Cmd().AddParameter("--switches=",
+ instance.switches_socket_path());
}
- AddTapFdParameter(&crosvm_cmd, instance.mobile_tap_name());
- AddTapFdParameter(&crosvm_cmd, instance.ethernet_tap_name());
- auto wifi_tap = AddTapFdParameter(&crosvm_cmd, instance.wifi_tap_name());
+ SharedFD wifi_tap;
+ // GPU capture can only support named files and not file descriptors due to
+ // having to pass arguments to crosvm via a wrapper script.
+ if (!gpu_capture_enabled) {
+ crosvm_cmd.AddTap(instance.mobile_tap_name());
+ crosvm_cmd.AddTap(instance.ethernet_tap_name());
+
+ // TODO(b/199103204): remove this as well when
+ // PRODUCT_ENFORCE_MAC80211_HWSIM is removed
+#ifndef ENFORCE_MAC80211_HWSIM
+ wifi_tap = crosvm_cmd.AddTap(instance.wifi_tap_name());
+#endif
+ }
if (FileExists(instance.access_kregistry_path())) {
- crosvm_cmd.AddParameter("--rw-pmem-device=",
- instance.access_kregistry_path());
+ crosvm_cmd.Cmd().AddParameter("--rw-pmem-device=",
+ instance.access_kregistry_path());
+ }
+
+ if (FileExists(instance.hwcomposer_pmem_path())) {
+ crosvm_cmd.Cmd().AddParameter("--rw-pmem-device=",
+ instance.hwcomposer_pmem_path());
}
if (FileExists(instance.pstore_path())) {
- crosvm_cmd.AddParameter("--pstore=path=", instance.pstore_path(),
- ",size=", FileSize(instance.pstore_path()));
+ crosvm_cmd.Cmd().AddParameter("--pstore=path=", instance.pstore_path(),
+ ",size=", FileSize(instance.pstore_path()));
}
if (config.enable_sandbox()) {
@@ -294,19 +236,20 @@
<< " does not exist " << std::endl;
return {};
}
- crosvm_cmd.AddParameter("--seccomp-policy-dir=", config.seccomp_policy_dir());
+ crosvm_cmd.Cmd().AddParameter("--seccomp-policy-dir=",
+ config.seccomp_policy_dir());
} else {
- crosvm_cmd.AddParameter("--disable-sandbox");
+ crosvm_cmd.Cmd().AddParameter("--disable-sandbox");
}
if (instance.vsock_guest_cid() >= 2) {
- crosvm_cmd.AddParameter("--cid=", instance.vsock_guest_cid());
+ crosvm_cmd.Cmd().AddParameter("--cid=", instance.vsock_guest_cid());
}
// Use a virtio-console instance for the main kernel console. All
// messages will switch from earlycon to virtio-console after the driver
// is loaded, and crosvm will append to the kernel log automatically
- add_hvc_console(instance.kernel_log_pipe_name());
+ crosvm_cmd.AddHvcConsoleReadOnly(instance.kernel_log_pipe_name());
if (config.console()) {
// stdin is the only currently supported way to write data to a serial port in
@@ -314,18 +257,18 @@
// the serial port output is received by the console forwarder as crosvm may
// print other messages to stdout.
if (config.kgdb() || config.use_bootloader()) {
- add_serial_console(instance.console_out_pipe_name(),
- instance.console_in_pipe_name());
+ crosvm_cmd.AddSerialConsoleReadWrite(instance.console_out_pipe_name(),
+ instance.console_in_pipe_name());
// In kgdb mode, we have the interactive console on ttyS0 (both Android's
// console and kdb), so we can disable the virtio-console port usually
// allocated to Android's serial console, and redirect it to a sink. This
// ensures that that the PCI device assignments (and thus sepolicy) don't
// have to change
- add_hvc_sink();
+ crosvm_cmd.AddHvcSink();
} else {
- add_serial_sink();
- add_hvc(instance.console_out_pipe_name(),
- instance.console_in_pipe_name());
+ crosvm_cmd.AddSerialSink();
+ crosvm_cmd.AddHvcReadWrite(instance.console_out_pipe_name(),
+ instance.console_in_pipe_name());
}
} else {
// Use an 8250 UART (ISA or platform device) for earlycon, as the
@@ -333,86 +276,165 @@
// In kgdb mode, earlycon is an interactive console, and so early
// dmesg will go there instead of the kernel.log
if (config.kgdb() || config.use_bootloader()) {
- add_serial_console_ro(instance.kernel_log_pipe_name());
+ crosvm_cmd.AddSerialConsoleReadOnly(instance.kernel_log_pipe_name());
}
// as above, create a fake virtio-console 'sink' port when the serial
// console is disabled, so the PCI device ID assignments don't move
// around
- add_hvc_sink();
+ crosvm_cmd.AddHvcSink();
}
- if (config.enable_gnss_grpc_proxy()) {
- add_serial(instance.gnss_out_pipe_name(), instance.gnss_in_pipe_name());
- }
-
- SharedFD log_out_rd, log_out_wr;
- if (!SharedFD::Pipe(&log_out_rd, &log_out_wr)) {
- LOG(ERROR) << "Failed to create log pipe for crosvm's stdout/stderr: "
- << log_out_rd->StrError();
+ auto crosvm_logs_path = instance.PerInstanceInternalPath("crosvm.fifo");
+ auto crosvm_logs = SharedFD::Fifo(crosvm_logs_path, 0666);
+ if (!crosvm_logs->IsOpen()) {
+ LOG(FATAL) << "Failed to create log fifo for crosvm's stdout/stderr: "
+ << crosvm_logs->StrError();
return {};
}
- crosvm_cmd.RedirectStdIO(Subprocess::StdIOChannel::kStdOut, log_out_wr);
- crosvm_cmd.RedirectStdIO(Subprocess::StdIOChannel::kStdErr, log_out_wr);
- Command log_tee_cmd(HostBinaryPath("log_tee"));
- log_tee_cmd.AddParameter("--process_name=crosvm");
- log_tee_cmd.AddParameter("--log_fd_in=", log_out_rd);
+ Command crosvm_log_tee_cmd(HostBinaryPath("log_tee"));
+ crosvm_log_tee_cmd.AddParameter("--process_name=crosvm");
+ crosvm_log_tee_cmd.AddParameter("--log_fd_in=", crosvm_logs);
// Serial port for logcat, redirected to a pipe
- add_hvc_ro(instance.logcat_pipe_name());
+ crosvm_cmd.AddHvcReadOnly(instance.logcat_pipe_name());
- add_hvc(instance.PerInstanceInternalPath("keymaster_fifo_vm.out"),
- instance.PerInstanceInternalPath("keymaster_fifo_vm.in"));
- add_hvc(instance.PerInstanceInternalPath("gatekeeper_fifo_vm.out"),
- instance.PerInstanceInternalPath("gatekeeper_fifo_vm.in"));
+ crosvm_cmd.AddHvcReadWrite(
+ instance.PerInstanceInternalPath("keymaster_fifo_vm.out"),
+ instance.PerInstanceInternalPath("keymaster_fifo_vm.in"));
+ crosvm_cmd.AddHvcReadWrite(
+ instance.PerInstanceInternalPath("gatekeeper_fifo_vm.out"),
+ instance.PerInstanceInternalPath("gatekeeper_fifo_vm.in"));
if (config.enable_host_bluetooth()) {
- add_hvc(instance.PerInstanceInternalPath("bt_fifo_vm.out"),
- instance.PerInstanceInternalPath("bt_fifo_vm.in"));
+ crosvm_cmd.AddHvcReadWrite(
+ instance.PerInstanceInternalPath("bt_fifo_vm.out"),
+ instance.PerInstanceInternalPath("bt_fifo_vm.in"));
} else {
- add_hvc_sink();
+ crosvm_cmd.AddHvcSink();
}
+ if (config.enable_gnss_grpc_proxy()) {
+ crosvm_cmd.AddHvcReadWrite(
+ instance.PerInstanceInternalPath("gnsshvc_fifo_vm.out"),
+ instance.PerInstanceInternalPath("gnsshvc_fifo_vm.in"));
+ crosvm_cmd.AddHvcReadWrite(
+ instance.PerInstanceInternalPath("locationhvc_fifo_vm.out"),
+ instance.PerInstanceInternalPath("locationhvc_fifo_vm.in"));
+ } else {
+ for (auto i = 0; i < 2; i++) {
+ crosvm_cmd.AddHvcSink();
+ }
+ }
+
for (auto i = 0; i < VmManager::kMaxDisks - disk_num; i++) {
- add_hvc_sink();
+ crosvm_cmd.AddHvcSink();
}
- CHECK(hvc_num + disk_num == VmManager::kMaxDisks + VmManager::kDefaultNumHvcs)
- << "HVC count (" << hvc_num << ") + disk count (" << disk_num << ") "
- << "is not the expected total of "
+ CHECK(crosvm_cmd.HvcNum() + disk_num ==
+ VmManager::kMaxDisks + VmManager::kDefaultNumHvcs)
+ << "HVC count (" << crosvm_cmd.HvcNum() << ") + disk count (" << disk_num
+ << ") is not the expected total of "
<< VmManager::kMaxDisks + VmManager::kDefaultNumHvcs << " devices";
if (config.enable_audio()) {
- crosvm_cmd.AddParameter("--sound=",
- config.ForDefaultInstance().audio_server_path());
+ crosvm_cmd.Cmd().AddParameter(
+ "--sound=", config.ForDefaultInstance().audio_server_path());
}
// TODO(b/162071003): virtiofs crashes without sandboxing, this should be fixed
- if (config.enable_sandbox()) {
+ if (0 && config.enable_sandbox()) {
// Set up directory shared with virtiofs
- crosvm_cmd.AddParameter("--shared-dir=", instance.PerInstancePath(kSharedDirName),
- ":shared:type=fs");
+ crosvm_cmd.Cmd().AddParameter(
+ "--shared-dir=", instance.PerInstancePath(kSharedDirName),
+ ":shared:type=fs");
}
// This needs to be the last parameter
- crosvm_cmd.AddParameter("--bios=", config.bootloader());
+ crosvm_cmd.Cmd().AddParameter("--bios=", config.bootloader());
+ // TODO(b/199103204): remove this as well when PRODUCT_ENFORCE_MAC80211_HWSIM
+ // is removed
// Only run the leases workaround if we are not using the new network
// bridge architecture - in that case, we have a wider DHCP address
// space and stale leases should be much less of an issue
- if (!FileExists("/var/run/cuttlefish-dnsmasq-cvd-wbr.leases")) {
+ if (!FileExists("/var/run/cuttlefish-dnsmasq-cvd-wbr.leases") &&
+ wifi_tap->IsOpen()) {
// TODO(schuffelen): QEMU also needs this and this is not the best place for
// this code. Find a better place to put it.
auto lease_file =
ForCurrentInstance("/var/run/cuttlefish-dnsmasq-cvd-wbr-") + ".leases";
- if (!ReleaseDhcpLeases(lease_file, wifi_tap)) {
+
+ std::uint8_t dhcp_server_ip[] = {
+ 192, 168, 96, (std::uint8_t)(ForCurrentInstance(1) * 4 - 3)};
+ if (!ReleaseDhcpLeases(lease_file, wifi_tap, dhcp_server_ip)) {
LOG(ERROR) << "Failed to release wifi DHCP leases. Connecting to the wifi "
<< "network may not work.";
}
}
std::vector<Command> ret;
- ret.push_back(std::move(crosvm_cmd));
- ret.push_back(std::move(log_tee_cmd));
+
+ if (gpu_capture_enabled) {
+ const std::string gpu_capture_basename =
+ cpp_basename(config.gpu_capture_binary());
+
+ auto gpu_capture_logs_path =
+ instance.PerInstanceInternalPath("gpu_capture.fifo");
+ auto gpu_capture_logs = SharedFD::Fifo(gpu_capture_logs_path, 0666);
+ if (!gpu_capture_logs->IsOpen()) {
+ LOG(FATAL)
+ << "Failed to create log fifo for gpu capture's stdout/stderr: "
+ << gpu_capture_logs->StrError();
+ return {};
+ }
+
+ Command gpu_capture_log_tee_cmd(HostBinaryPath("log_tee"));
+ gpu_capture_log_tee_cmd.AddParameter("--process_name=",
+ gpu_capture_basename);
+ gpu_capture_log_tee_cmd.AddParameter("--log_fd_in=", gpu_capture_logs);
+
+ Command gpu_capture_command(config.gpu_capture_binary());
+ if (gpu_capture_basename == "ngfx") {
+ // Crosvm depends on command line arguments being passed as multiple
+ // arguments but ngfx only allows a single `--args`. To work around this,
+ // create a wrapper script that launches crosvm with all of the arguments
+ // and pass this wrapper script to ngfx.
+ const std::string crosvm_wrapper_path =
+ instance.PerInstanceInternalPath("crosvm_wrapper.sh");
+ const std::string crosvm_wrapper_content =
+ crosvm_cmd.Cmd().AsBashScript(crosvm_logs_path);
+
+ CHECK(android::base::WriteStringToFile(crosvm_wrapper_content,
+ crosvm_wrapper_path));
+ CHECK(MakeFileExecutable(crosvm_wrapper_path));
+
+ gpu_capture_command.AddParameter("--exe=", crosvm_wrapper_path);
+ gpu_capture_command.AddParameter("--launch-detached");
+ gpu_capture_command.AddParameter("--verbose");
+ gpu_capture_command.AddParameter("--activity=Frame Debugger");
+ } else {
+ // TODO(natsu): renderdoc
+ LOG(FATAL) << "Unhandled GPU capture binary: "
+ << config.gpu_capture_binary();
+ }
+
+ gpu_capture_command.RedirectStdIO(Subprocess::StdIOChannel::kStdOut,
+ gpu_capture_logs);
+ gpu_capture_command.RedirectStdIO(Subprocess::StdIOChannel::kStdErr,
+ gpu_capture_logs);
+
+ ret.push_back(std::move(gpu_capture_log_tee_cmd));
+ ret.push_back(std::move(gpu_capture_command));
+ } else {
+ crosvm_cmd.Cmd().RedirectStdIO(Subprocess::StdIOChannel::kStdOut,
+ crosvm_logs);
+ crosvm_cmd.Cmd().RedirectStdIO(Subprocess::StdIOChannel::kStdErr,
+ crosvm_logs);
+
+ ret.push_back(std::move(crosvm_cmd.Cmd()));
+ }
+
+ ret.push_back(std::move(crosvm_log_tee_cmd));
return ret;
}
diff --git a/host/libs/vm_manager/crosvm_manager.h b/host/libs/vm_manager/crosvm_manager.h
index 9d41d68..bfd5b79 100644
--- a/host/libs/vm_manager/crosvm_manager.h
+++ b/host/libs/vm_manager/crosvm_manager.h
@@ -31,11 +31,11 @@
class CrosvmManager : public VmManager {
public:
static std::string name() { return "crosvm"; }
- CrosvmManager(Arch arch) : VmManager(arch) {}
virtual ~CrosvmManager() = default;
bool IsSupported() override;
- std::vector<std::string> ConfigureGpuMode(const std::string&) override;
+ std::vector<std::string> ConfigureGraphics(
+ const CuttlefishConfig& config) override;
std::string ConfigureBootDevices(int num_disks) override;
std::vector<cuttlefish::Command> StartCommands(
diff --git a/host/libs/vm_manager/gem5_manager.cpp b/host/libs/vm_manager/gem5_manager.cpp
new file mode 100644
index 0000000..ef9d2cd
--- /dev/null
+++ b/host/libs/vm_manager/gem5_manager.cpp
@@ -0,0 +1,188 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "host/libs/vm_manager/gem5_manager.h"
+
+#include <string.h>
+#include <sys/socket.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <sys/un.h>
+#include <sys/wait.h>
+#include <unistd.h>
+
+#include <cstdlib>
+#include <fstream>
+#include <sstream>
+#include <string>
+#include <thread>
+#include <vector>
+
+#include <android-base/strings.h>
+#include <android-base/logging.h>
+#include <vulkan/vulkan.h>
+
+#include "common/libs/fs/shared_select.h"
+#include "common/libs/utils/files.h"
+#include "common/libs/utils/subprocess.h"
+#include "common/libs/utils/users.h"
+#include "host/libs/config/cuttlefish_config.h"
+#include "host/libs/config/known_paths.h"
+
+namespace cuttlefish {
+namespace vm_manager {
+namespace {
+
+void LogAndSetEnv(const char* key, const std::string& value) {
+ setenv(key, value.c_str(), 1);
+ LOG(INFO) << key << "=" << value;
+}
+
+void GenerateGem5File(const CuttlefishConfig& config) {
+ // Gem5 specific config, currently users have to change these config locally (without throug launch_cvd input flag) to meet their design
+ // TODO: Add these config into launch_cvd input flag or parse from one json file
+ std::string cpu_class = "AtomicSimpleCPU";
+ std::string l1_icache_class = "None";
+ std::string l1_dcache_class = "None";
+ std::string walk_cache_class = "None";
+ std::string l2_Cache_class = "None";
+ std::string cpu_freq = "4GHz";
+ int num_cores = 1;
+ std::string mem_type = "DDR3_1600_8x8";
+ int mem_channels = 1;
+ std::string mem_ranks = "None";
+
+ // start generating starter_fs.py
+ std::string fs_path = config.gem5_binary_dir() + "/configs/example/arm/starter_fs.py";
+ std::ofstream starter_fs_ofstream(fs_path.c_str());
+ starter_fs_ofstream << fs_header << "\n";
+
+ // global vars in python
+ starter_fs_ofstream << "default_disk = 'linaro-minimal-aarch64.img'\n";
+
+ // main function
+ starter_fs_ofstream << "def main():\n";
+
+ // args
+ auto instance = config.ForDefaultInstance();
+ starter_fs_ofstream << " parser = argparse.ArgumentParser(epilog=__doc__)\n";
+ starter_fs_ofstream << " parser.add_argument(\"--disk-image\", action=\"append\", type=str, default=[])\n";
+ starter_fs_ofstream << " parser.add_argument(\"--mem-type\", default=\"" << mem_type << "\", choices=ObjectList.mem_list.get_names())\n";
+ starter_fs_ofstream << " parser.add_argument(\"--mem-channels\", type=int, default=" << mem_channels << ")\n";
+ starter_fs_ofstream << " parser.add_argument(\"--mem-ranks\", type=int, default=" << mem_ranks << ")\n";
+ starter_fs_ofstream << " parser.add_argument(\"--mem-size\", action=\"store\", type=str, default=\"" << config.memory_mb() << "MB\")\n";
+ starter_fs_ofstream << " args = parser.parse_args()\n";
+
+ // instantiate system
+ starter_fs_ofstream << " root = Root(full_system=True)\n";
+ starter_fs_ofstream << " mem_mode = " << cpu_class << ".memory_mode()\n";
+ starter_fs_ofstream << " has_caches = True if mem_mode == \"timing\" else False\n";
+ starter_fs_ofstream << " root.system = devices.SimpleSystem(has_caches, args.mem_size, mem_mode=mem_mode, workload=ArmFsLinux(object_file=SysPaths.binary(\"" << config.assembly_dir() << "/kernel\")))\n";
+
+ // mem config and pci instantiate
+ starter_fs_ofstream << fs_mem_pci;
+
+ // system settings
+ starter_fs_ofstream << " root.system.cpu_cluster = [devices.CpuCluster(root.system, " << num_cores << ", \"" << cpu_freq << "\", \"1.0V\", " << cpu_class << ", " << l1_icache_class << ", " << l1_dcache_class << ", " << walk_cache_class << ", " << l2_Cache_class << ")]\n";
+ starter_fs_ofstream << " root.system.addCaches(has_caches, last_cache_level=2)\n";
+ starter_fs_ofstream << " root.system.realview.setupBootLoader(root.system, SysPaths.binary)\n";
+ starter_fs_ofstream << " root.system.workload.dtb_filename = os.path.join(m5.options.outdir, 'system.dtb')\n";
+ starter_fs_ofstream << " root.system.generateDtb(root.system.workload.dtb_filename)\n";
+ starter_fs_ofstream << " root.system.workload.initrd_filename = \"" << instance.PerInstancePath("initrd.img") << "\"\n";
+
+ //kernel cmd
+ starter_fs_ofstream << fs_kernel_cmd << "\n";
+
+ // execute main
+ starter_fs_ofstream << fs_exe_main << "\n";
+}
+
+} // namespace
+
+Gem5Manager::Gem5Manager(Arch arch) : arch_(arch) {}
+
+bool Gem5Manager::IsSupported() {
+ return HostSupportsQemuCli();
+}
+
+std::vector<std::string> Gem5Manager::ConfigureGraphics(
+ const CuttlefishConfig& config) {
+ // TODO: Add support for the gem5 gpu models
+
+ // Override the default HAL search paths in all cases. We do this because
+ // the HAL search path allows for fallbacks, and fallbacks in conjunction
+ // with properities lead to non-deterministic behavior while loading the
+ // HALs.
+ return {
+ "androidboot.cpuvulkan.version=" + std::to_string(VK_API_VERSION_1_1),
+ "androidboot.hardware.gralloc=minigbm",
+ "androidboot.hardware.hwcomposer=" + config.hwcomposer(),
+ "androidboot.hardware.hwcomposer.mode=noop",
+ "androidboot.hardware.egl=angle",
+ "androidboot.hardware.vulkan=pastel",
+ };
+}
+
+std::string Gem5Manager::ConfigureBootDevices(int /*num_disks*/) {
+ switch (arch_) {
+ case Arch::Arm:
+ case Arch::Arm64:
+ return "androidboot.boot_devices=30000000.pci";
+ // TODO: Add x86 support
+ default:
+ return "";
+ }
+}
+
+std::vector<Command> Gem5Manager::StartCommands(
+ const CuttlefishConfig& config) {
+ auto instance = config.ForDefaultInstance();
+
+ auto stop = [](Subprocess* proc) {
+ return KillSubprocess(proc) == StopperResult::kStopSuccess
+ ? StopperResult::kStopCrash
+ : StopperResult::kStopFailure;
+ };
+ std::string gem5_binary = config.gem5_binary_dir();
+ switch (arch_) {
+ case Arch::Arm:
+ case Arch::Arm64:
+ gem5_binary += "/build/ARM/gem5.opt";
+ break;
+ case Arch::X86:
+ case Arch::X86_64:
+ gem5_binary += "/build/X86/gem5.opt";
+ break;
+ }
+ // generate Gem5 starter_fs.py before we execute it
+ GenerateGem5File(config);
+
+ Command gem5_cmd(gem5_binary, stop);
+ gem5_cmd.AddParameter(config.gem5_binary_dir(), "/configs/example/arm/starter_fs.py");
+ gem5_cmd.AddParameter("--mem-size=", config.memory_mb() * 1024ULL * 1024ULL);
+ for (const auto& disk : instance.virtual_disk_paths()) {
+ gem5_cmd.AddParameter("--disk-image=", disk);
+ }
+
+ LogAndSetEnv("M5_PATH", config.assembly_dir());
+
+ std::vector<Command> ret;
+ ret.push_back(std::move(gem5_cmd));
+ return ret;
+}
+
+} // namespace vm_manager
+} // namespace cuttlefish
diff --git a/host/libs/vm_manager/gem5_manager.h b/host/libs/vm_manager/gem5_manager.h
new file mode 100644
index 0000000..1c19683
--- /dev/null
+++ b/host/libs/vm_manager/gem5_manager.h
@@ -0,0 +1,114 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#pragma once
+
+#include <string>
+#include <vector>
+
+#include "host/libs/vm_manager/vm_manager.h"
+
+#include "common/libs/fs/shared_fd.h"
+
+namespace cuttlefish {
+namespace vm_manager {
+
+// Starts a guest VM using the gem5 command directly. It requires the host
+// package to support the gem5 capability.
+class Gem5Manager : public VmManager {
+ public:
+ static std::string name() { return "gem5"; }
+
+ Gem5Manager(Arch);
+ virtual ~Gem5Manager() = default;
+
+ bool IsSupported() override;
+ std::vector<std::string> ConfigureGraphics(
+ const CuttlefishConfig& config) override;
+ std::string ConfigureBootDevices(int num_disks) override;
+
+ std::vector<cuttlefish::Command> StartCommands(
+ const CuttlefishConfig& config) override;
+
+ private:
+ Arch arch_;
+};
+
+const std::string fs_header = R"CPP_STR_END(import argparse
+import devices
+import os
+import m5
+from m5.util import addToPath
+from m5.objects import *
+from m5.options import *
+from common import SysPaths
+from common import ObjectList
+from common import MemConfig
+from common.cores.arm import HPI
+m5.util.addToPath('../..')
+)CPP_STR_END";
+
+const std::string fs_mem_pci = R"CPP_STR_END(
+ MemConfig.config_mem(args, root.system)
+
+ pci_devices = []
+ pci_devices.append(PciVirtIO(vio=VirtIOConsole(device=Terminal(number=0))))
+ pci_devices.append(PciVirtIO(vio=VirtIOConsole(device=Terminal(number=1, outfile="none"))))
+ pci_devices.append(PciVirtIO(vio=VirtIOConsole(device=Terminal(number=2))))
+ pci_devices.append(PciVirtIO(vio=VirtIOConsole(device=Terminal(number=3, outfile="none"))))
+ pci_devices.append(PciVirtIO(vio=VirtIOConsole(device=Terminal(number=4, outfile="none"))))
+ pci_devices.append(PciVirtIO(vio=VirtIOConsole(device=Terminal(number=5, outfile="none"))))
+ pci_devices.append(PciVirtIO(vio=VirtIOConsole(device=Terminal(number=6, outfile="none"))))
+
+ for each_item in args.disk_image:
+ disk_image = CowDiskImage()
+ disk_image.child.image_file = SysPaths.disk(each_item)
+ pci_devices.append(PciVirtIO(vio=VirtIOBlock(image=disk_image)))
+
+ root.system.pci_devices = pci_devices
+ for pci_device in root.system.pci_devices:
+ root.system.attach_pci(pci_device)
+
+ root.system.connect()
+)CPP_STR_END";
+
+const std::string fs_kernel_cmd = R"CPP_STR_END(
+ kernel_cmd = [
+ "lpj=19988480",
+ "norandmaps",
+ "mem=%s" % args.mem_size,
+ "console=hvc0",
+ "panic=-1",
+ "earlycon=pl011,mmio32,0x1c090000",
+ "audit=1",
+ "printk.devkmsg=on",
+ "firmware_class.path=/vendor/etc/",
+ "kfence.sample_interval=500",
+ "loop.max_part=7",
+ "bootconfig",
+ "androidboot.force_normal_boot=1",
+ ]
+ root.system.workload.command_line = " ".join(kernel_cmd)
+ m5.instantiate()
+ sys.exit(m5.simulate().getCode())
+)CPP_STR_END";
+
+const std::string fs_exe_main = R"CPP_STR_END(
+if __name__ == "__m5_main__":
+ main()
+)CPP_STR_END";
+
+} // namespace vm_manager
+} // namespace cuttlefish
diff --git a/host/libs/vm_manager/qemu_manager.cpp b/host/libs/vm_manager/qemu_manager.cpp
index c8c1084..adedf04 100644
--- a/host/libs/vm_manager/qemu_manager.cpp
+++ b/host/libs/vm_manager/qemu_manager.cpp
@@ -85,15 +85,47 @@
return true;
}
+std::pair<int,int> GetQemuVersion(const std::string& qemu_binary)
+{
+ Command qemu_version_cmd(qemu_binary);
+ qemu_version_cmd.AddParameter("-version");
+
+ std::string qemu_version_input, qemu_version_output, qemu_version_error;
+ cuttlefish::SubprocessOptions options;
+ options.Verbose(false);
+ int qemu_version_ret =
+ cuttlefish::RunWithManagedStdio(std::move(qemu_version_cmd),
+ &qemu_version_input,
+ &qemu_version_output,
+ &qemu_version_error, options);
+ if (qemu_version_ret != 0) {
+ LOG(FATAL) << qemu_binary << " -version returned unexpected response "
+ << qemu_version_output << ". Stderr was " << qemu_version_error;
+ return { 0, 0 };
+ }
+
+ // Snip around the extra text we don't care about
+ qemu_version_output.erase(0, std::string("QEMU emulator version ").length());
+ auto space_pos = qemu_version_output.find(" ", 0);
+ if (space_pos != std::string::npos) {
+ qemu_version_output.resize(space_pos);
+ }
+
+ auto qemu_version_bits = android::base::Split(qemu_version_output, ".");
+ return { std::stoi(qemu_version_bits[0]), std::stoi(qemu_version_bits[1]) };
+}
+
} // namespace
+QemuManager::QemuManager(Arch arch) : arch_(arch) {}
+
bool QemuManager::IsSupported() {
return HostSupportsQemuCli();
}
-std::vector<std::string> QemuManager::ConfigureGpuMode(
- const std::string& gpu_mode) {
- if (gpu_mode == kGpuModeGuestSwiftshader) {
+std::vector<std::string> QemuManager::ConfigureGraphics(
+ const CuttlefishConfig& config) {
+ if (config.gpu_mode() == kGpuModeGuestSwiftshader) {
// Override the default HAL search paths in all cases. We do this because
// the HAL search path allows for fallbacks, and fallbacks in conjunction
// with properities lead to non-deterministic behavior while loading the
@@ -101,17 +133,17 @@
return {
"androidboot.cpuvulkan.version=" + std::to_string(VK_API_VERSION_1_1),
"androidboot.hardware.gralloc=minigbm",
- "androidboot.hardware.hwcomposer=ranchu",
- "androidboot.hardware.egl=swiftshader",
+ "androidboot.hardware.hwcomposer=" + config.hwcomposer(),
+ "androidboot.hardware.egl=angle",
"androidboot.hardware.vulkan=pastel",
};
}
- if (gpu_mode == kGpuModeDrmVirgl) {
+ if (config.gpu_mode() == kGpuModeDrmVirgl) {
return {
"androidboot.cpuvulkan.version=0",
"androidboot.hardware.gralloc=minigbm",
- "androidboot.hardware.hwcomposer=drm_minigbm",
+ "androidboot.hardware.hwcomposer=drm",
"androidboot.hardware.egl=mesa",
};
}
@@ -124,7 +156,8 @@
case Arch::X86:
case Arch::X86_64: {
// QEMU has additional PCI devices for an ISA bridge and PIIX4
- return ConfigureMultipleBootDevices("pci0000:00/0000:00:", 2, num_disks);
+ // virtio_gpu precedes the first console or disk
+ return ConfigureMultipleBootDevices("pci0000:00/0000:00:", 3, num_disks);
}
case Arch::Arm:
return "androidboot.boot_devices=3f000000.pcie";
@@ -140,11 +173,13 @@
auto stop = [](Subprocess* proc) {
auto stopped = Stop();
if (stopped) {
- return true;
+ return StopperResult::kStopSuccess;
}
LOG(WARNING) << "Failed to stop VMM nicely, "
<< "attempting to KILL";
- return KillSubprocess(proc);
+ return KillSubprocess(proc) == StopperResult::kStopSuccess
+ ? StopperResult::kStopCrash
+ : StopperResult::kStopFailure;
};
std::string qemu_binary = config.qemu_binary_dir();
switch (arch_) {
@@ -161,6 +196,8 @@
qemu_binary += "/qemu-system-x86_64";
break;
}
+
+ auto qemu_version = GetQemuVersion(qemu_binary);
Command qemu_cmd(qemu_binary, stop);
int hvc_num = 0;
@@ -228,6 +265,7 @@
};
bool is_arm = arch_ == Arch::Arm || arch_ == Arch::Arm64;
+ bool is_arm64 = arch_ == Arch::Arm64;
auto access_kregistry_size_bytes = 0;
if (FileExists(instance.access_kregistry_path())) {
@@ -237,6 +275,14 @@
<< access_kregistry_size_bytes << ") not a multiple of 1MB";
}
+ auto hwcomposer_pmem_size_bytes = 0;
+ if (FileExists(instance.hwcomposer_pmem_path())) {
+ hwcomposer_pmem_size_bytes = FileSize(instance.hwcomposer_pmem_path());
+ CHECK((hwcomposer_pmem_size_bytes & (1024 * 1024 - 1)) == 0)
+ << instance.hwcomposer_pmem_path() << " file size ("
+ << hwcomposer_pmem_size_bytes << ") not a multiple of 1MB";
+ }
+
auto pstore_size_bytes = 0;
if (FileExists(instance.pstore_path())) {
pstore_size_bytes = FileSize(instance.pstore_path());
@@ -249,13 +295,28 @@
qemu_cmd.AddParameter("guest=", instance.instance_name(), ",debug-threads=on");
qemu_cmd.AddParameter("-machine");
- auto machine = is_arm ? "virt,gic-version=2,mte=on"
- : "pc-i440fx-2.8,accel=kvm,nvdimm=on";
+ std::string machine = is_arm ? "virt" : "pc-i440fx-2.8,nvdimm=on";
+ if (IsHostCompatible(arch_)) {
+ machine += ",accel=kvm";
+ if (is_arm) {
+ machine += ",gic-version=3";
+ }
+ } else if (is_arm) {
+ // QEMU doesn't support GICv3 with TCG yet
+ machine += ",gic-version=2";
+ if (is_arm64) {
+ // Only enable MTE in TCG mode. We haven't started to run on ARMv8/ARMv9
+ // devices with KVM and MTE, so MTE will always require TCG
+ machine += ",mte=on";
+ }
+ CHECK(config.cpus() <= 8) << "CPUs must be no more than 8 with GICv2";
+ }
qemu_cmd.AddParameter(machine, ",usb=off,dump-guest-core=off");
qemu_cmd.AddParameter("-m");
auto maxmem = config.memory_mb() +
- access_kregistry_size_bytes / 1024 / 1024 +
+ (access_kregistry_size_bytes / 1024 / 1024) +
+ (hwcomposer_pmem_size_bytes / 1024 / 1024) +
(is_arm ? 0 : pstore_size_bytes / 1024 / 1024);
auto slots = is_arm ? "" : ",slots=2";
qemu_cmd.AddParameter("size=", config.memory_mb(), "M",
@@ -292,16 +353,40 @@
qemu_cmd.AddParameter("-chardev");
qemu_cmd.AddParameter("socket,id=charmonitor,path=", GetMonitorPath(config),
- ",server,nowait");
+ ",server=on,wait=off");
qemu_cmd.AddParameter("-mon");
qemu_cmd.AddParameter("chardev=charmonitor,id=monitor,mode=control");
+ if (config.gpu_mode() == kGpuModeDrmVirgl) {
+ qemu_cmd.AddParameter("-display");
+ qemu_cmd.AddParameter("egl-headless");
+
+ qemu_cmd.AddParameter("-vnc");
+ qemu_cmd.AddParameter(":", instance.qemu_vnc_server_port());
+ } else {
+ qemu_cmd.AddParameter("-display");
+ qemu_cmd.AddParameter("none");
+ }
+
+ auto display_configs = config.display_configs();
+ CHECK_GE(display_configs.size(), 1);
+ auto display_config = display_configs[0];
+
+ qemu_cmd.AddParameter("-device");
+
+ bool use_gpu_gl = qemu_version.first >= 6 &&
+ config.gpu_mode() != kGpuModeGuestSwiftshader;
+ qemu_cmd.AddParameter(use_gpu_gl ?
+ "virtio-gpu-gl-pci" : "virtio-gpu-pci", ",id=gpu0",
+ ",xres=", display_config.width,
+ ",yres=", display_config.height);
+
// In kgdb mode, earlycon is an interactive console, and so early
// dmesg will go there instead of the kernel.log. On QEMU, we do this
// bit of logic up before the hvc console is set up, so the command line
// flags appear in the right order and "append=on" does the right thing
- if (!(config.console() && (config.kgdb() || config.use_bootloader()))) {
+ if (!config.console() && (config.kgdb() || config.use_bootloader())) {
add_serial_console_ro(instance.kernel_log_pipe_name());
}
@@ -336,10 +421,6 @@
add_hvc_sink();
}
- if (config.enable_gnss_grpc_proxy()) {
- add_serial_console(instance.gnss_pipe_prefix());
- }
-
// Serial port for logcat, redirected to a pipe
add_hvc_ro(instance.logcat_pipe_name());
@@ -351,6 +432,15 @@
add_hvc_sink();
}
+ if (config.enable_gnss_grpc_proxy()) {
+ add_hvc(instance.PerInstanceInternalPath("gnsshvc_fifo_vm"));
+ add_hvc(instance.PerInstanceInternalPath("locationhvc_fifo_vm"));
+ } else {
+ for (auto i = 0; i < 2; i++) {
+ add_hvc_sink();
+ }
+ }
+
auto disk_num = instance.virtual_disk_paths().size();
for (auto i = 0; i < VmManager::kMaxDisks - disk_num; i++) {
@@ -378,23 +468,12 @@
",id=virtio-disk", i, bootindex);
}
- if (config.gpu_mode() == kGpuModeDrmVirgl) {
- qemu_cmd.AddParameter("-display");
- qemu_cmd.AddParameter("egl-headless");
-
- qemu_cmd.AddParameter("-vnc");
- qemu_cmd.AddParameter(":", instance.vnc_server_port() - 5900);
- } else {
- qemu_cmd.AddParameter("-display");
- qemu_cmd.AddParameter("none");
- }
-
if (!is_arm && FileExists(instance.pstore_path())) {
// QEMU will assign the NVDIMM (ramoops pstore region) 100000000-1001fffff
// As we will pass this to ramoops, define this region first so it is always
// located at this address. This is currently x86 only.
qemu_cmd.AddParameter("-object");
- qemu_cmd.AddParameter("memory-backend-file,id=objpmem0,share,mem-path=",
+ qemu_cmd.AddParameter("memory-backend-file,id=objpmem0,share=on,mem-path=",
instance.pstore_path(), ",size=", pstore_size_bytes);
qemu_cmd.AddParameter("-device");
@@ -403,14 +482,29 @@
// QEMU does not implement virtio-pmem-pci for ARM64 yet; restore this
// when the device has been added
- if (!is_arm && FileExists(instance.access_kregistry_path())) {
- qemu_cmd.AddParameter("-object");
- qemu_cmd.AddParameter("memory-backend-file,id=objpmem1,share,mem-path=",
- instance.access_kregistry_path(), ",size=",
- access_kregistry_size_bytes);
+ if (!is_arm) {
+ if (access_kregistry_size_bytes > 0) {
+ qemu_cmd.AddParameter("-object");
+ qemu_cmd.AddParameter(
+ "memory-backend-file,id=objpmem1,share=on,mem-path=",
+ instance.access_kregistry_path(),
+ ",size=", access_kregistry_size_bytes);
- qemu_cmd.AddParameter("-device");
- qemu_cmd.AddParameter("virtio-pmem-pci,disable-legacy=on,memdev=objpmem1,id=pmem0");
+ qemu_cmd.AddParameter("-device");
+ qemu_cmd.AddParameter(
+ "virtio-pmem-pci,disable-legacy=on,memdev=objpmem1,id=pmem0");
+ }
+ if (hwcomposer_pmem_size_bytes > 0) {
+ qemu_cmd.AddParameter("-object");
+ qemu_cmd.AddParameter(
+ "memory-backend-file,id=objpmem2,share=on,mem-path=",
+ instance.hwcomposer_pmem_path(),
+ ",size=", hwcomposer_pmem_size_bytes);
+
+ qemu_cmd.AddParameter("-device");
+ qemu_cmd.AddParameter(
+ "virtio-pmem-pci,disable-legacy=on,memdev=objpmem2,id=pmem1");
+ }
}
qemu_cmd.AddParameter("-object");
@@ -421,10 +515,14 @@
"max-bytes=1024,period=2000");
qemu_cmd.AddParameter("-device");
- qemu_cmd.AddParameter("virtio-mouse-pci");
+ qemu_cmd.AddParameter("virtio-mouse-pci,disable-legacy=on");
qemu_cmd.AddParameter("-device");
- qemu_cmd.AddParameter("virtio-keyboard-pci");
+ qemu_cmd.AddParameter("virtio-keyboard-pci,disable-legacy=on");
+
+ // device padding for unsupported "switches" input
+ qemu_cmd.AddParameter("-device");
+ qemu_cmd.AddParameter("virtio-keyboard-pci,disable-legacy=on");
auto vhost_net = config.vhost_net() ? ",vhost=on" : "";
@@ -444,16 +542,13 @@
qemu_cmd.AddParameter("-device");
qemu_cmd.AddParameter("virtio-net-pci-non-transitional,netdev=hostnet1,id=net1");
-
+#ifndef ENFORCE_MAC80211_HWSIM
qemu_cmd.AddParameter("-netdev");
qemu_cmd.AddParameter("tap,id=hostnet2,ifname=", instance.wifi_tap_name(),
",script=no,downscript=no", vhost_net);
-
qemu_cmd.AddParameter("-device");
qemu_cmd.AddParameter("virtio-net-pci-non-transitional,netdev=hostnet2,id=net2");
-
- qemu_cmd.AddParameter("-device");
- qemu_cmd.AddParameter("virtio-gpu-pci,id=gpu0");
+#endif
qemu_cmd.AddParameter("-cpu");
qemu_cmd.AddParameter(IsHostCompatible(arch_) ? "host" : "max");
diff --git a/host/libs/vm_manager/qemu_manager.h b/host/libs/vm_manager/qemu_manager.h
index 934a976..f213ca2 100644
--- a/host/libs/vm_manager/qemu_manager.h
+++ b/host/libs/vm_manager/qemu_manager.h
@@ -31,15 +31,19 @@
public:
static std::string name() { return "qemu_cli"; }
- QemuManager(Arch arch) : VmManager(arch) {}
+ QemuManager(Arch);
virtual ~QemuManager() = default;
bool IsSupported() override;
- std::vector<std::string> ConfigureGpuMode(const std::string&) override;
+ std::vector<std::string> ConfigureGraphics(
+ const CuttlefishConfig& config) override;
std::string ConfigureBootDevices(int num_disks) override;
std::vector<cuttlefish::Command> StartCommands(
const CuttlefishConfig& config) override;
+
+ private:
+ Arch arch_;
};
} // namespace vm_manager
diff --git a/host/libs/vm_manager/vm_manager.cpp b/host/libs/vm_manager/vm_manager.cpp
index 8c2ef57..2726f0c 100644
--- a/host/libs/vm_manager/vm_manager.cpp
+++ b/host/libs/vm_manager/vm_manager.cpp
@@ -17,12 +17,14 @@
#include "host/libs/vm_manager/vm_manager.h"
#include <android-base/logging.h>
+#include <fruit/fruit.h>
#include <iomanip>
#include <memory>
#include "host/libs/config/cuttlefish_config.h"
#include "host/libs/vm_manager/crosvm_manager.h"
+#include "host/libs/vm_manager/gem5_manager.h"
#include "host/libs/vm_manager/qemu_manager.h"
namespace cuttlefish {
@@ -32,8 +34,10 @@
std::unique_ptr<VmManager> vmm;
if (name == QemuManager::name()) {
vmm.reset(new QemuManager(arch));
+ } else if (name == Gem5Manager::name()) {
+ vmm.reset(new Gem5Manager(arch));
} else if (name == CrosvmManager::name()) {
- vmm.reset(new CrosvmManager(arch));
+ vmm.reset(new CrosvmManager());
}
if (!vmm) {
LOG(ERROR) << "Invalid VM manager: " << name;
@@ -61,6 +65,16 @@
return {boot_devices_prop};
}
+fruit::Component<fruit::Required<const CuttlefishConfig>, VmManager>
+VmManagerComponent() {
+ return fruit::createComponent().registerProvider(
+ [](const CuttlefishConfig& config) {
+ auto vmm = GetVmManager(config.vm_manager(), config.target_arch());
+ CHECK(vmm) << "Invalid VMM/Arch: \"" << config.vm_manager() << "\""
+ << (int)config.target_arch() << "\"";
+ return vmm.release(); // fruit takes ownership of raw pointers
+ });
+}
+
} // namespace vm_manager
} // namespace cuttlefish
-
diff --git a/host/libs/vm_manager/vm_manager.h b/host/libs/vm_manager/vm_manager.h
index b538e8f..7a81a97 100644
--- a/host/libs/vm_manager/vm_manager.h
+++ b/host/libs/vm_manager/vm_manager.h
@@ -14,21 +14,18 @@
* limitations under the License.
*/
#pragma once
+#include <common/libs/utils/subprocess.h>
+#include <fruit/fruit.h>
+#include <host/libs/config/cuttlefish_config.h>
#include <string>
#include <vector>
-#include <common/libs/utils/subprocess.h>
-#include <host/libs/config/cuttlefish_config.h>
-
namespace cuttlefish {
namespace vm_manager {
// Superclass of every guest VM manager.
class VmManager {
- protected:
- const Arch arch_;
-
public:
// This is the number of HVC virtual console ports that should be configured
// by the VmManager. Because crosvm currently allocates these ports as the
@@ -40,7 +37,7 @@
// need to consume host resources, except for the PCI ID. Use this trick to
// keep the number of PCI IDs assigned constant for all flags/vm manager
// combinations
- static const int kDefaultNumHvcs = 6;
+ static const int kDefaultNumHvcs = 8;
// This is the number of virtual disks (block devices) that should be
// configured by the VmManager. Related to the description above regarding
@@ -58,11 +55,11 @@
// the persistent disk
static const int kDefaultNumBootDevices = 2;
- VmManager(Arch arch) : arch_(arch) {}
virtual ~VmManager() = default;
virtual bool IsSupported() = 0;
- virtual std::vector<std::string> ConfigureGpuMode(const std::string&) = 0;
+ virtual std::vector<std::string> ConfigureGraphics(
+ const CuttlefishConfig& config) = 0;
virtual std::string ConfigureBootDevices(int num_disks) = 0;
// Starts the VMM. It will usually build a command and pass it to the
@@ -73,6 +70,9 @@
const CuttlefishConfig& config) = 0;
};
+fruit::Component<fruit::Required<const CuttlefishConfig>, VmManager>
+VmManagerComponent();
+
std::unique_ptr<VmManager> GetVmManager(const std::string&, Arch arch);
std::string ConfigureMultipleBootDevices(const std::string& pci_path, int pci_offset,
diff --git a/host/libs/web/Android.bp b/host/libs/web/Android.bp
new file mode 100644
index 0000000..a0bc146
--- /dev/null
+++ b/host/libs/web/Android.bp
@@ -0,0 +1,61 @@
+//
+// Copyright (C) 2019 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package {
+ default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+cc_library {
+ name: "libcuttlefish_web",
+ srcs: [
+ "build_api.cc",
+ "credential_source.cc",
+ "curl_wrapper.cc",
+ "install_zip.cc",
+ ],
+ static_libs: [
+ "libcuttlefish_host_config",
+ "libext2_blkid",
+ ],
+ target: {
+ host: {
+ static_libs: [
+ "libbase",
+ "libcuttlefish_fs",
+ "libcuttlefish_utils",
+ "libcurl",
+ "libcrypto",
+ "liblog",
+ "libssl",
+ "libz",
+ "libjsoncpp",
+ ],
+ },
+ android: {
+ shared_libs: [
+ "libbase",
+ "libcuttlefish_fs",
+ "libcuttlefish_utils",
+ "libcurl",
+ "libcrypto",
+ "liblog",
+ "libssl",
+ "libz",
+ "libjsoncpp",
+ ],
+ },
+ },
+ defaults: ["cuttlefish_host"],
+}
diff --git a/host/libs/web/build_api.cc b/host/libs/web/build_api.cc
new file mode 100644
index 0000000..85d32bd
--- /dev/null
+++ b/host/libs/web/build_api.cc
@@ -0,0 +1,353 @@
+//
+// Copyright (C) 2019 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "build_api.h"
+
+#include <dirent.h>
+#include <unistd.h>
+
+#include <chrono>
+#include <set>
+#include <string>
+#include <thread>
+
+#include <android-base/strings.h>
+#include <android-base/logging.h>
+
+#include "common/libs/utils/environment.h"
+#include "common/libs/utils/files.h"
+
+namespace cuttlefish {
+namespace {
+
+const std::string BUILD_API =
+ "https://www.googleapis.com/android/internal/build/v3";
+
+bool StatusIsTerminal(const std::string& status) {
+ const static std::set<std::string> terminal_statuses = {
+ "abandoned",
+ "complete",
+ "error",
+ "ABANDONED",
+ "COMPLETE",
+ "ERROR",
+ };
+ return terminal_statuses.count(status) > 0;
+}
+
+} // namespace
+
+Artifact::Artifact(const Json::Value& json_artifact) {
+ name = json_artifact["name"].asString();
+ size = std::stol(json_artifact["size"].asString());
+ last_modified_time = std::stol(json_artifact["lastModifiedTime"].asString());
+ md5 = json_artifact["md5"].asString();
+ content_type = json_artifact["contentType"].asString();
+ revision = json_artifact["revision"].asString();
+ creation_time = std::stol(json_artifact["creationTime"].asString());
+ crc32 = json_artifact["crc32"].asUInt();
+}
+
+std::ostream& operator<<(std::ostream& out, const DeviceBuild& build) {
+ return out << "(id=\"" << build.id << "\", target=\"" << build.target << "\")";
+}
+
+std::ostream& operator<<(std::ostream& out, const DirectoryBuild& build) {
+ auto paths = android::base::Join(build.paths, ":");
+ return out << "(paths=\"" << paths << "\", target=\"" << build.target << "\")";
+}
+
+std::ostream& operator<<(std::ostream& out, const Build& build) {
+ std::visit([&out](auto&& arg) { out << arg; }, build);
+ return out;
+}
+
+DirectoryBuild::DirectoryBuild(const std::vector<std::string>& paths,
+ const std::string& target)
+ : paths(paths), target(target), id("eng") {
+ product = StringFromEnv("TARGET_PRODUCT", "");
+}
+
+BuildApi::BuildApi(CurlWrapper& curl, CredentialSource* credential_source)
+ : BuildApi(curl, credential_source, "") {}
+
+BuildApi::BuildApi(CurlWrapper& curl, CredentialSource* credential_source,
+ std::string api_key)
+ : curl(curl),
+ credential_source(credential_source),
+ api_key_(std::move(api_key)) {}
+
+std::vector<std::string> BuildApi::Headers() {
+ std::vector<std::string> headers;
+ if (credential_source) {
+ headers.push_back("Authorization: Bearer " +
+ credential_source->Credential());
+ }
+ return headers;
+}
+
+std::string BuildApi::LatestBuildId(const std::string& branch,
+ const std::string& target) {
+ std::string url =
+ BUILD_API + "/builds?branch=" + curl.UrlEscape(branch) +
+ "&buildAttemptStatus=complete" +
+ "&buildType=submitted&maxResults=1&successful=true&target=" +
+ curl.UrlEscape(target);
+ if (!api_key_.empty()) {
+ url += "&key=" + curl.UrlEscape(api_key_);
+ }
+ auto curl_response = curl.DownloadToJson(url, Headers());
+ const auto& json = curl_response.data;
+ if (!curl_response.HttpSuccess()) {
+ LOG(FATAL) << "Error fetching the latest build of \"" << target
+ << "\" on \"" << branch << "\". The server response was \""
+ << json << "\", and code was " << curl_response.http_code;
+ }
+ CHECK(!json.isMember("error"))
+ << "Response had \"error\" but had http success status. Received \""
+ << json << "\"";
+
+ if (!json.isMember("builds") || json["builds"].size() != 1) {
+ LOG(WARNING) << "expected to receive 1 build for \"" << target << "\" on \""
+ << branch << "\", but received " << json["builds"].size()
+ << ". Full response was " << json;
+ return "";
+ }
+ return json["builds"][0]["buildId"].asString();
+}
+
+std::string BuildApi::BuildStatus(const DeviceBuild& build) {
+ std::string url = BUILD_API + "/builds/" + curl.UrlEscape(build.id) + "/" +
+ curl.UrlEscape(build.target);
+ if (!api_key_.empty()) {
+ url += "?key=" + curl.UrlEscape(api_key_);
+ }
+ auto curl_response = curl.DownloadToJson(url, Headers());
+ const auto& json = curl_response.data;
+ if (!curl_response.HttpSuccess()) {
+ LOG(FATAL) << "Error fetching the status of \"" << build
+ << "\". The server response was \"" << json
+ << "\", and code was " << curl_response.http_code;
+ }
+ CHECK(!json.isMember("error"))
+ << "Response had \"error\" but had http success status. Received \""
+ << json << "\"";
+
+ return json["buildAttemptStatus"].asString();
+}
+
+std::string BuildApi::ProductName(const DeviceBuild& build) {
+ std::string url = BUILD_API + "/builds/" + curl.UrlEscape(build.id) + "/" +
+ curl.UrlEscape(build.target);
+ if (!api_key_.empty()) {
+ url += "?key=" + curl.UrlEscape(api_key_);
+ }
+ auto curl_response = curl.DownloadToJson(url, Headers());
+ const auto& json = curl_response.data;
+ if (!curl_response.HttpSuccess()) {
+ LOG(FATAL) << "Error fetching the product name of \"" << build
+ << "\". The server response was \"" << json
+ << "\", and code was " << curl_response.http_code;
+ }
+ CHECK(!json.isMember("error"))
+ << "Response had \"error\" but had http success status. Received \""
+ << json << "\"";
+
+ CHECK(json.isMember("target")) << "Build was missing target field.";
+ return json["target"]["product"].asString();
+}
+
+std::vector<Artifact> BuildApi::Artifacts(const DeviceBuild& build) {
+ std::string page_token = "";
+ std::vector<Artifact> artifacts;
+ do {
+ std::string url = BUILD_API + "/builds/" + curl.UrlEscape(build.id) + "/" +
+ curl.UrlEscape(build.target) +
+ "/attempts/latest/artifacts?maxResults=100";
+ if (page_token != "") {
+ url += "&pageToken=" + curl.UrlEscape(page_token);
+ }
+ if (!api_key_.empty()) {
+ url += "&key=" + curl.UrlEscape(api_key_);
+ }
+ auto curl_response = curl.DownloadToJson(url, Headers());
+ const auto& json = curl_response.data;
+ if (!curl_response.HttpSuccess()) {
+ LOG(FATAL) << "Error fetching the artifacts of \"" << build
+ << "\". The server response was \"" << json
+ << "\", and code was " << curl_response.http_code;
+ }
+ CHECK(!json.isMember("error"))
+ << "Response had \"error\" but had http success status. Received \""
+ << json << "\"";
+ if (json.isMember("nextPageToken")) {
+ page_token = json["nextPageToken"].asString();
+ } else {
+ page_token = "";
+ }
+ for (const auto& artifact_json : json["artifacts"]) {
+ artifacts.emplace_back(artifact_json);
+ }
+ } while (page_token != "");
+ return artifacts;
+}
+
+struct CloseDir {
+ void operator()(DIR* dir) {
+ closedir(dir);
+ }
+};
+
+using UniqueDir = std::unique_ptr<DIR, CloseDir>;
+
+std::vector<Artifact> BuildApi::Artifacts(const DirectoryBuild& build) {
+ std::vector<Artifact> artifacts;
+ for (const auto& path : build.paths) {
+ auto dir = UniqueDir(opendir(path.c_str()));
+ CHECK(dir != nullptr) << "Could not read files from \"" << path << "\"";
+ for (auto entity = readdir(dir.get()); entity != nullptr; entity = readdir(dir.get())) {
+ artifacts.emplace_back(std::string(entity->d_name));
+ }
+ }
+ return artifacts;
+}
+
+bool BuildApi::ArtifactToCallback(const DeviceBuild& build,
+ const std::string& artifact,
+ CurlWrapper::DataCallback callback) {
+ std::string download_url_endpoint =
+ BUILD_API + "/builds/" + curl.UrlEscape(build.id) + "/" +
+ curl.UrlEscape(build.target) + "/attempts/latest/artifacts/" +
+ curl.UrlEscape(artifact) + "/url";
+ if (!api_key_.empty()) {
+ download_url_endpoint += "?key=" + curl.UrlEscape(api_key_);
+ }
+ auto curl_response = curl.DownloadToJson(download_url_endpoint, Headers());
+ const auto& json = curl_response.data;
+ if (!(curl_response.HttpSuccess() || curl_response.HttpRedirect())) {
+ LOG(ERROR) << "Error fetching the url of \"" << artifact << "\" for \""
+ << build << "\". The server response was \"" << json
+ << "\", and code was " << curl_response.http_code;
+ return false;
+ }
+ if (json.isMember("error")) {
+ LOG(ERROR) << "Response had \"error\" but had http success status. "
+ << "Received \"" << json << "\"";
+ return false;
+ }
+ if (!json.isMember("signedUrl")) {
+ LOG(ERROR) << "URL endpoint did not have json path: " << json;
+ return false;
+ }
+ std::string url = json["signedUrl"].asString();
+ return curl.DownloadToCallback(callback, url).HttpSuccess();
+}
+
+bool BuildApi::ArtifactToFile(const DeviceBuild& build,
+ const std::string& artifact,
+ const std::string& path) {
+ std::string download_url_endpoint =
+ BUILD_API + "/builds/" + curl.UrlEscape(build.id) + "/" +
+ curl.UrlEscape(build.target) + "/attempts/latest/artifacts/" +
+ curl.UrlEscape(artifact) + "/url";
+ if (!api_key_.empty()) {
+ download_url_endpoint += "?key=" + curl.UrlEscape(api_key_);
+ }
+ auto curl_response = curl.DownloadToJson(download_url_endpoint, Headers());
+ const auto& json = curl_response.data;
+ if (!(curl_response.HttpSuccess() || curl_response.HttpRedirect())) {
+ LOG(ERROR) << "Error fetching the url of \"" << artifact << "\" for \""
+ << build << "\". The server response was \"" << json
+ << "\", and code was " << curl_response.http_code;
+ return false;
+ }
+ if (json.isMember("error")) {
+ LOG(ERROR) << "Response had \"error\" but had http success status. "
+ << "Received \"" << json << "\"";
+ }
+ if (!json.isMember("signedUrl")) {
+ LOG(ERROR) << "URL endpoint did not have json path: " << json;
+ return false;
+ }
+ std::string url = json["signedUrl"].asString();
+ return curl.DownloadToFile(url, path).HttpSuccess();
+}
+
+bool BuildApi::ArtifactToFile(const DirectoryBuild& build,
+ const std::string& artifact,
+ const std::string& destination) {
+ for (const auto& path : build.paths) {
+ auto source = path + "/" + artifact;
+ if (!FileExists(source)) {
+ continue;
+ }
+ unlink(destination.c_str());
+ if (symlink(source.c_str(), destination.c_str())) {
+ int error_num = errno;
+ LOG(ERROR) << "Could not create symlink from " << source << " to "
+ << destination << ": " << strerror(error_num);
+ return false;
+ }
+ return true;
+ }
+ return false;
+}
+
+Build ArgumentToBuild(BuildApi* build_api, const std::string& arg,
+ const std::string& default_build_target,
+ const std::chrono::seconds& retry_period) {
+ if (arg.find(':') != std::string::npos) {
+ std::vector<std::string> dirs = android::base::Split(arg, ":");
+ std::string id = dirs.back();
+ dirs.pop_back();
+ return DirectoryBuild(dirs, id);
+ }
+ size_t slash_pos = arg.find('/');
+ if (slash_pos != std::string::npos
+ && arg.find('/', slash_pos + 1) != std::string::npos) {
+ LOG(FATAL) << "Build argument cannot have more than one '/' slash. Was at "
+ << slash_pos << " and " << arg.find('/', slash_pos + 1);
+ }
+ std::string build_target = slash_pos == std::string::npos
+ ? default_build_target : arg.substr(slash_pos + 1);
+ std::string branch_or_id = slash_pos == std::string::npos
+ ? arg: arg.substr(0, slash_pos);
+ std::string branch_latest_build_id =
+ build_api->LatestBuildId(branch_or_id, build_target);
+ std::string build_id = branch_or_id;
+ if (branch_latest_build_id != "") {
+ LOG(INFO) << "The latest good build on branch \"" << branch_or_id
+ << "\"with build target \"" << build_target
+ << "\" is \"" << branch_latest_build_id << "\"";
+ build_id = branch_latest_build_id;
+ }
+ DeviceBuild proposed_build = DeviceBuild(build_id, build_target);
+ std::string status = build_api->BuildStatus(proposed_build);
+ if (status == "") {
+ LOG(FATAL) << proposed_build << " is not a valid branch or build id.";
+ }
+ LOG(INFO) << "Status for build " << proposed_build << " is " << status;
+ while (retry_period != std::chrono::seconds::zero() && !StatusIsTerminal(status)) {
+ LOG(INFO) << "Status is \"" << status << "\". Waiting for " << retry_period.count()
+ << " seconds.";
+ std::this_thread::sleep_for(retry_period);
+ status = build_api->BuildStatus(proposed_build);
+ }
+ LOG(INFO) << "Status for build " << proposed_build << " is " << status;
+ proposed_build.product = build_api->ProductName(proposed_build);
+ return proposed_build;
+}
+
+} // namespace cuttlefish
diff --git a/host/commands/fetcher/build_api.h b/host/libs/web/build_api.h
similarity index 90%
rename from host/commands/fetcher/build_api.h
rename to host/libs/web/build_api.h
index 505ad4b..7a78389 100644
--- a/host/commands/fetcher/build_api.h
+++ b/host/libs/web/build_api.h
@@ -81,12 +81,9 @@
std::ostream& operator<<(std::ostream&, const Build&);
class BuildApi {
- CurlWrapper curl;
- std::unique_ptr<CredentialSource> credential_source;
-
- std::vector<std::string> Headers();
-public:
- BuildApi(std::unique_ptr<CredentialSource> credential_source);
+ public:
+ BuildApi(CurlWrapper&, CredentialSource*);
+ BuildApi(CurlWrapper&, CredentialSource*, std::string api_key);
~BuildApi() = default;
std::string LatestBuildId(const std::string& branch,
@@ -98,6 +95,9 @@
std::vector<Artifact> Artifacts(const DeviceBuild&);
+ bool ArtifactToCallback(const DeviceBuild& build, const std::string& artifact,
+ CurlWrapper::DataCallback callback);
+
bool ArtifactToFile(const DeviceBuild& build, const std::string& artifact,
const std::string& path);
@@ -116,6 +116,13 @@
return ArtifactToFile(arg, artifact, path);
}, build);
}
+
+ private:
+ std::vector<std::string> Headers();
+
+ CurlWrapper& curl;
+ CredentialSource* credential_source;
+ std::string api_key_;
};
Build ArgumentToBuild(BuildApi* api, const std::string& arg,
diff --git a/host/libs/web/credential_source.cc b/host/libs/web/credential_source.cc
new file mode 100644
index 0000000..c12c494
--- /dev/null
+++ b/host/libs/web/credential_source.cc
@@ -0,0 +1,313 @@
+//
+// Copyright (C) 2019 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "credential_source.h"
+
+#include <android-base/logging.h>
+#include <android-base/strings.h>
+#include <json/json.h>
+#include <openssl/bio.h>
+#include <openssl/evp.h>
+#include <openssl/pem.h>
+
+#include "common/libs/utils/base64.h"
+
+namespace cuttlefish {
+namespace {
+
+std::chrono::steady_clock::duration REFRESH_WINDOW =
+ std::chrono::minutes(2);
+std::string REFRESH_URL = "http://metadata.google.internal/computeMetadata/"
+ "v1/instance/service-accounts/default/token";
+
+} // namespace
+
+GceMetadataCredentialSource::GceMetadataCredentialSource(CurlWrapper& curl)
+ : curl(curl) {
+ latest_credential = "";
+ expiration = std::chrono::steady_clock::now();
+}
+
+std::string GceMetadataCredentialSource::Credential() {
+ if (expiration - std::chrono::steady_clock::now() < REFRESH_WINDOW) {
+ RefreshCredential();
+ }
+ return latest_credential;
+}
+
+void GceMetadataCredentialSource::RefreshCredential() {
+ auto curl_response =
+ curl.DownloadToJson(REFRESH_URL, {"Metadata-Flavor: Google"});
+ const auto& json = curl_response.data;
+ if (!curl_response.HttpSuccess()) {
+ LOG(FATAL) << "Error fetching credentials. The server response was \""
+ << json << "\", and code was " << curl_response.http_code;
+ }
+ CHECK(!json.isMember("error"))
+ << "Response had \"error\" but had http success status. Received \""
+ << json << "\"";
+
+ bool has_access_token = json.isMember("access_token");
+ bool has_expires_in = json.isMember("expires_in");
+ if (!has_access_token || !has_expires_in) {
+ LOG(FATAL) << "GCE credential was missing access_token or expires_in. "
+ << "Full response was " << json << "";
+ }
+
+ expiration = std::chrono::steady_clock::now() +
+ std::chrono::seconds(json["expires_in"].asInt());
+ latest_credential = json["access_token"].asString();
+}
+
+std::unique_ptr<CredentialSource> GceMetadataCredentialSource::make(
+ CurlWrapper& curl) {
+ return std::unique_ptr<CredentialSource>(
+ new GceMetadataCredentialSource(curl));
+}
+
+FixedCredentialSource::FixedCredentialSource(const std::string& credential) {
+ this->credential = credential;
+}
+
+std::string FixedCredentialSource::Credential() {
+ return credential;
+}
+
+std::unique_ptr<CredentialSource> FixedCredentialSource::make(
+ const std::string& credential) {
+ return std::unique_ptr<CredentialSource>(new FixedCredentialSource(credential));
+}
+
+Result<RefreshCredentialSource> RefreshCredentialSource::FromOauth2ClientFile(
+ CurlWrapper& curl, std::istream& stream) {
+ Json::CharReaderBuilder builder;
+ std::unique_ptr<Json::CharReader> reader(builder.newCharReader());
+ Json::Value json;
+ std::string errorMessage;
+ CF_EXPECT(Json::parseFromStream(builder, stream, &json, &errorMessage),
+ "Failed to parse json: " << errorMessage);
+ CF_EXPECT(json.isMember("data"));
+ auto& data = json["data"];
+ CF_EXPECT(data.type() == Json::ValueType::arrayValue);
+
+ CF_EXPECT(data.size() == 1);
+ auto& data_first = data[0];
+ CF_EXPECT(data_first.type() == Json::ValueType::objectValue);
+
+ CF_EXPECT(data_first.isMember("credential"));
+ auto& credential = data_first["credential"];
+ CF_EXPECT(credential.type() == Json::ValueType::objectValue);
+
+ CF_EXPECT(credential.isMember("client_id"));
+ auto& client_id = credential["client_id"];
+ CF_EXPECT(client_id.type() == Json::ValueType::stringValue);
+
+ CF_EXPECT(credential.isMember("client_secret"));
+ auto& client_secret = credential["client_secret"];
+ CF_EXPECT(client_secret.type() == Json::ValueType::stringValue);
+
+ CF_EXPECT(credential.isMember("token_response"));
+ auto& token_response = credential["token_response"];
+ CF_EXPECT(token_response.type() == Json::ValueType::objectValue);
+
+ CF_EXPECT(token_response.isMember("refresh_token"));
+ auto& refresh_token = credential["refresh_token"];
+ CF_EXPECT(refresh_token.type() == Json::ValueType::stringValue);
+
+ return RefreshCredentialSource(curl, client_id.asString(),
+ client_secret.asString(),
+ refresh_token.asString());
+}
+
+RefreshCredentialSource::RefreshCredentialSource(
+ CurlWrapper& curl, const std::string& client_id,
+ const std::string& client_secret, const std::string& refresh_token)
+ : curl_(curl),
+ client_id_(client_id),
+ client_secret_(client_secret),
+ refresh_token_(refresh_token) {}
+
+std::string RefreshCredentialSource::Credential() {
+ if (expiration_ - std::chrono::steady_clock::now() < REFRESH_WINDOW) {
+ UpdateLatestCredential();
+ }
+ return latest_credential_;
+}
+
+void RefreshCredentialSource::UpdateLatestCredential() {
+ std::vector<std::string> headers = {
+ "Content-Type: application/x-www-form-urlencoded"};
+ std::stringstream data;
+ data << "client_id=" << curl_.UrlEscape(client_id_) << "&";
+ data << "client_secret=" << curl_.UrlEscape(client_secret_) << "&";
+ data << "refresh_token=" << curl_.UrlEscape(refresh_token_) << "&";
+ data << "grant_type=refresh_token";
+
+ static constexpr char kUrl[] = "https://oauth2.googleapis.com/token";
+ auto response = curl_.PostToJson(kUrl, data.str(), headers);
+ CHECK(response.HttpSuccess()) << response.data;
+ auto& json = response.data;
+
+ CHECK(!json.isMember("error"))
+ << "Response had \"error\" but had http success status. Received \""
+ << json << "\"";
+
+ bool has_access_token = json.isMember("access_token");
+ bool has_expires_in = json.isMember("expires_in");
+ CHECK(has_access_token && has_expires_in)
+ << "GCE credential was missing access_token or expires_in. "
+ << "Full response was " << json << "";
+
+ expiration_ = std::chrono::steady_clock::now() +
+ std::chrono::seconds(json["expires_in"].asInt());
+ latest_credential_ = json["access_token"].asString();
+}
+
+static std::string CollectSslErrors() {
+ std::stringstream errors;
+ auto callback = [](const char* str, size_t len, void* stream) {
+ ((std::stringstream*)stream)->write(str, len);
+ return 1; // success
+ };
+ ERR_print_errors_cb(callback, &errors);
+ return errors.str();
+}
+
+Result<ServiceAccountOauthCredentialSource>
+ServiceAccountOauthCredentialSource::FromJson(CurlWrapper& curl,
+ const Json::Value& json,
+ const std::string& scope) {
+ ServiceAccountOauthCredentialSource source(curl);
+ source.scope_ = scope;
+
+ CF_EXPECT(json.isMember("client_email"));
+ CF_EXPECT(json["client_email"].type() == Json::ValueType::stringValue);
+ source.email_ = json["client_email"].asString();
+
+ CF_EXPECT(json.isMember("private_key"));
+ CF_EXPECT(json["private_key"].type() == Json::ValueType::stringValue);
+ std::string key_str = json["private_key"].asString();
+
+ std::unique_ptr<BIO, int (*)(BIO*)> bo(CF_EXPECT(BIO_new(BIO_s_mem())),
+ BIO_free);
+ CF_EXPECT(BIO_write(bo.get(), key_str.c_str(), key_str.size()) ==
+ key_str.size());
+
+ auto pkey = CF_EXPECT(PEM_read_bio_PrivateKey(bo.get(), nullptr, 0, 0),
+ CollectSslErrors());
+ source.private_key_.reset(pkey);
+
+ return source;
+}
+
+ServiceAccountOauthCredentialSource::ServiceAccountOauthCredentialSource(
+ CurlWrapper& curl)
+ : curl_(curl), private_key_(nullptr, EVP_PKEY_free) {}
+
+static std::string Base64Url(const char* data, std::size_t size) {
+ std::string base64;
+ CHECK(EncodeBase64(data, size, &base64));
+ base64 = android::base::StringReplace(base64, "+", "-", /* all */ true);
+ base64 = android::base::StringReplace(base64, "/", "_", /* all */ true);
+ return base64;
+}
+
+static std::string JsonToBase64Url(const Json::Value& json) {
+ Json::StreamWriterBuilder factory;
+ auto serialized = Json::writeString(factory, json);
+ return Base64Url(serialized.c_str(), serialized.size());
+}
+
+static std::string CreateJwt(const std::string& email, const std::string& scope,
+ EVP_PKEY* private_key) {
+ using std::chrono::duration_cast;
+ using std::chrono::minutes;
+ using std::chrono::seconds;
+ using std::chrono::system_clock;
+ // https://developers.google.com/identity/protocols/oauth2/service-account
+ Json::Value header_json;
+ header_json["alg"] = "RS256";
+ header_json["typ"] = "JWT";
+ std::string header_str = JsonToBase64Url(header_json);
+
+ Json::Value claim_set_json;
+ claim_set_json["iss"] = email;
+ claim_set_json["scope"] = scope;
+ claim_set_json["aud"] = "https://oauth2.googleapis.com/token";
+ auto time = system_clock::now();
+ claim_set_json["iat"] =
+ (uint64_t)duration_cast<seconds>(time.time_since_epoch()).count();
+ auto exp = time + minutes(30);
+ claim_set_json["exp"] =
+ (uint64_t)duration_cast<seconds>(exp.time_since_epoch()).count();
+ std::string claim_set_str = JsonToBase64Url(claim_set_json);
+
+ std::string jwt_to_sign = header_str + "." + claim_set_str;
+
+ std::unique_ptr<EVP_MD_CTX, void (*)(EVP_MD_CTX*)> sign_ctx(
+ EVP_MD_CTX_create(), EVP_MD_CTX_free);
+ CHECK(EVP_DigestSignInit(sign_ctx.get(), nullptr, EVP_sha256(), nullptr,
+ private_key));
+ CHECK(EVP_DigestSignUpdate(sign_ctx.get(), jwt_to_sign.c_str(),
+ jwt_to_sign.size()));
+ size_t length;
+ CHECK(EVP_DigestSignFinal(sign_ctx.get(), nullptr, &length));
+ std::vector<uint8_t> sig_raw(length);
+ CHECK(EVP_DigestSignFinal(sign_ctx.get(), sig_raw.data(), &length));
+
+ return jwt_to_sign + "." + Base64Url((const char*)sig_raw.data(), length);
+}
+
+void ServiceAccountOauthCredentialSource::RefreshCredential() {
+ static constexpr char URL[] = "https://oauth2.googleapis.com/token";
+ static constexpr char GRANT[] = "urn:ietf:params:oauth:grant-type:jwt-bearer";
+ std::stringstream content;
+ content << "grant_type=" << curl_.UrlEscape(GRANT) << "&";
+ auto jwt = CreateJwt(email_, scope_, private_key_.get());
+ content << "assertion=" << curl_.UrlEscape(jwt);
+ std::vector<std::string> headers = {
+ "Content-Type: application/x-www-form-urlencoded"};
+ auto curl_response = curl_.PostToJson(URL, content.str(), headers);
+ if (!curl_response.HttpSuccess()) {
+ LOG(FATAL) << "Error fetching credentials. The server response was \""
+ << curl_response.data << "\", and code was "
+ << curl_response.http_code;
+ }
+ Json::Value json = curl_response.data;
+
+ CHECK(!json.isMember("error"))
+ << "Response had \"error\" but had http success status. Received \""
+ << json << "\"";
+
+ bool has_access_token = json.isMember("access_token");
+ bool has_expires_in = json.isMember("expires_in");
+ if (!has_access_token || !has_expires_in) {
+ LOG(FATAL) << "GCE credential was missing access_token or expires_in. "
+ << "Full response was " << json << "";
+ }
+
+ expiration_ = std::chrono::steady_clock::now() +
+ std::chrono::seconds(json["expires_in"].asInt());
+ latest_credential_ = json["access_token"].asString();
+}
+
+std::string ServiceAccountOauthCredentialSource::Credential() {
+ if (expiration_ - std::chrono::steady_clock::now() < REFRESH_WINDOW) {
+ RefreshCredential();
+ }
+ return latest_credential_;
+}
+
+} // namespace cuttlefish
diff --git a/host/libs/web/credential_source.h b/host/libs/web/credential_source.h
new file mode 100644
index 0000000..7ab64e1
--- /dev/null
+++ b/host/libs/web/credential_source.h
@@ -0,0 +1,105 @@
+//
+// Copyright (C) 2019 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#pragma once
+
+#include <chrono>
+#include <memory>
+
+#include <json/json.h>
+#include <openssl/evp.h>
+
+#include "common/libs/utils/result.h"
+#include "host/libs/web/curl_wrapper.h"
+
+namespace cuttlefish {
+
+class CredentialSource {
+public:
+ virtual ~CredentialSource() = default;
+ virtual std::string Credential() = 0;
+};
+
+class GceMetadataCredentialSource : public CredentialSource {
+ CurlWrapper& curl;
+ std::string latest_credential;
+ std::chrono::steady_clock::time_point expiration;
+
+ void RefreshCredential();
+public:
+ GceMetadataCredentialSource(CurlWrapper&);
+ GceMetadataCredentialSource(GceMetadataCredentialSource&&) = default;
+
+ virtual std::string Credential();
+
+ static std::unique_ptr<CredentialSource> make(CurlWrapper&);
+};
+
+class FixedCredentialSource : public CredentialSource {
+ std::string credential;
+public:
+ FixedCredentialSource(const std::string& credential);
+
+ virtual std::string Credential();
+
+ static std::unique_ptr<CredentialSource> make(const std::string& credential);
+};
+
+class RefreshCredentialSource : public CredentialSource {
+ public:
+ static Result<RefreshCredentialSource> FromOauth2ClientFile(
+ CurlWrapper& curl, std::istream& stream);
+
+ RefreshCredentialSource(CurlWrapper& curl, const std::string& client_id,
+ const std::string& client_secret,
+ const std::string& refresh_token);
+
+ std::string Credential() override;
+
+ private:
+ void UpdateLatestCredential();
+
+ CurlWrapper& curl_;
+ std::string client_id_;
+ std::string client_secret_;
+ std::string refresh_token_;
+
+ std::string latest_credential_;
+ std::chrono::steady_clock::time_point expiration_;
+};
+
+class ServiceAccountOauthCredentialSource : public CredentialSource {
+ public:
+ static Result<ServiceAccountOauthCredentialSource> FromJson(
+ CurlWrapper& curl, const Json::Value& service_account_json,
+ const std::string& scope);
+ ServiceAccountOauthCredentialSource(ServiceAccountOauthCredentialSource&&) =
+ default;
+
+ std::string Credential() override;
+
+ private:
+ ServiceAccountOauthCredentialSource(CurlWrapper& curl);
+ void RefreshCredential();
+
+ CurlWrapper& curl_;
+ std::string email_;
+ std::string scope_;
+ std::unique_ptr<EVP_PKEY, void (*)(EVP_PKEY*)> private_key_;
+
+ std::string latest_credential_;
+ std::chrono::steady_clock::time_point expiration_;
+};
+}
diff --git a/host/libs/web/curl_wrapper.cc b/host/libs/web/curl_wrapper.cc
new file mode 100644
index 0000000..b81d498
--- /dev/null
+++ b/host/libs/web/curl_wrapper.cc
@@ -0,0 +1,403 @@
+//
+// Copyright (C) 2019 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "host/libs/web/curl_wrapper.h"
+
+#include <stdio.h>
+
+#include <fstream>
+#include <mutex>
+#include <sstream>
+#include <string>
+#include <thread>
+
+#include <android-base/logging.h>
+#include <curl/curl.h>
+#include <json/json.h>
+
+namespace cuttlefish {
+namespace {
+
+size_t curl_to_function_cb(char* ptr, size_t, size_t nmemb, void* userdata) {
+ CurlWrapper::DataCallback* callback = (CurlWrapper::DataCallback*)userdata;
+ if (!(*callback)(ptr, nmemb)) {
+ return 0; // Signals error to curl
+ }
+ return nmemb;
+}
+
+size_t file_write_callback(char *ptr, size_t, size_t nmemb, void *userdata) {
+ std::stringstream* stream = (std::stringstream*) userdata;
+ stream->write(ptr, nmemb);
+ return nmemb;
+}
+
+curl_slist* build_slist(const std::vector<std::string>& strings) {
+ curl_slist* curl_headers = nullptr;
+ for (const auto& str : strings) {
+ curl_slist* temp = curl_slist_append(curl_headers, str.c_str());
+ if (temp == nullptr) {
+ LOG(ERROR) << "curl_slist_append failed to add " << str;
+ if (curl_headers) {
+ curl_slist_free_all(curl_headers);
+ return nullptr;
+ }
+ }
+ curl_headers = temp;
+ }
+ return curl_headers;
+}
+
+class CurlWrapperImpl : public CurlWrapper {
+ public:
+ CurlWrapperImpl() {
+ curl_ = curl_easy_init();
+ if (!curl_) {
+ LOG(ERROR) << "failed to initialize curl";
+ return;
+ }
+ }
+ ~CurlWrapperImpl() { curl_easy_cleanup(curl_); }
+
+ CurlResponse<std::string> PostToString(
+ const std::string& url, const std::string& data_to_write,
+ const std::vector<std::string>& headers) override {
+ std::lock_guard<std::mutex> lock(mutex_);
+ LOG(INFO) << "Attempting to download \"" << url << "\"";
+ if (!curl_) {
+ LOG(ERROR) << "curl was not initialized\n";
+ return {"", -1};
+ }
+ curl_slist* curl_headers = build_slist(headers);
+ curl_easy_reset(curl_);
+ curl_easy_setopt(curl_, CURLOPT_CAINFO,
+ "/etc/ssl/certs/ca-certificates.crt");
+ curl_easy_setopt(curl_, CURLOPT_HTTPHEADER, curl_headers);
+ curl_easy_setopt(curl_, CURLOPT_URL, url.c_str());
+ curl_easy_setopt(curl_, CURLOPT_POSTFIELDSIZE, data_to_write.size());
+ curl_easy_setopt(curl_, CURLOPT_POSTFIELDS, data_to_write.c_str());
+ std::stringstream data_to_read;
+ curl_easy_setopt(curl_, CURLOPT_WRITEFUNCTION, file_write_callback);
+ curl_easy_setopt(curl_, CURLOPT_WRITEDATA, &data_to_read);
+ char error_buf[CURL_ERROR_SIZE];
+ curl_easy_setopt(curl_, CURLOPT_ERRORBUFFER, error_buf);
+ curl_easy_setopt(curl_, CURLOPT_VERBOSE, 1L);
+ CURLcode res = curl_easy_perform(curl_);
+ if (curl_headers) {
+ curl_slist_free_all(curl_headers);
+ }
+ if (res != CURLE_OK) {
+ LOG(ERROR) << "curl_easy_perform() failed. "
+ << "Code was \"" << res << "\". "
+ << "Strerror was \"" << curl_easy_strerror(res) << "\". "
+ << "Error buffer was \"" << error_buf << "\".";
+ return {"", -1};
+ }
+ long http_code = 0;
+ curl_easy_getinfo(curl_, CURLINFO_RESPONSE_CODE, &http_code);
+ return {data_to_read.str(), http_code};
+ }
+
+ CurlResponse<Json::Value> PostToJson(
+ const std::string& url, const std::string& data_to_write,
+ const std::vector<std::string>& headers) override {
+ CurlResponse<std::string> response =
+ PostToString(url, data_to_write, headers);
+ const std::string& contents = response.data;
+ Json::CharReaderBuilder builder;
+ std::unique_ptr<Json::CharReader> reader(builder.newCharReader());
+ Json::Value json;
+ std::string errorMessage;
+ if (!reader->parse(&*contents.begin(), &*contents.end(), &json,
+ &errorMessage)) {
+ LOG(ERROR) << "Could not parse json: " << errorMessage;
+ json["error"] = "Failed to parse json.";
+ json["response"] = contents;
+ }
+ return {json, response.http_code};
+ }
+
+ CurlResponse<Json::Value> PostToJson(
+ const std::string& url, const Json::Value& data_to_write,
+ const std::vector<std::string>& headers) override {
+ std::stringstream json_str;
+ json_str << data_to_write;
+ return PostToJson(url, json_str.str(), headers);
+ }
+
+ CurlResponse<bool> DownloadToCallback(
+ DataCallback callback, const std::string& url,
+ const std::vector<std::string>& headers) {
+ std::lock_guard<std::mutex> lock(mutex_);
+ LOG(INFO) << "Attempting to download \"" << url << "\"";
+ if (!curl_) {
+ LOG(ERROR) << "curl was not initialized\n";
+ return {false, -1};
+ }
+ if (!callback(nullptr, 0)) { // Signal start of data
+ LOG(ERROR) << "Callback failure\n";
+ return {false, -1};
+ }
+ curl_slist* curl_headers = build_slist(headers);
+ curl_easy_reset(curl_);
+ curl_easy_setopt(curl_, CURLOPT_CAINFO,
+ "/etc/ssl/certs/ca-certificates.crt");
+ curl_easy_setopt(curl_, CURLOPT_HTTPHEADER, curl_headers);
+ curl_easy_setopt(curl_, CURLOPT_URL, url.c_str());
+ curl_easy_setopt(curl_, CURLOPT_WRITEFUNCTION, curl_to_function_cb);
+ curl_easy_setopt(curl_, CURLOPT_WRITEDATA, &callback);
+ char error_buf[CURL_ERROR_SIZE];
+ curl_easy_setopt(curl_, CURLOPT_ERRORBUFFER, error_buf);
+ curl_easy_setopt(curl_, CURLOPT_VERBOSE, 1L);
+ CURLcode res = curl_easy_perform(curl_);
+ if (curl_headers) {
+ curl_slist_free_all(curl_headers);
+ }
+ if (res != CURLE_OK) {
+ LOG(ERROR) << "curl_easy_perform() failed. "
+ << "Code was \"" << res << "\". "
+ << "Strerror was \"" << curl_easy_strerror(res) << "\". "
+ << "Error buffer was \"" << error_buf << "\".";
+ return {false, -1};
+ }
+ long http_code = 0;
+ curl_easy_getinfo(curl_, CURLINFO_RESPONSE_CODE, &http_code);
+ return {true, http_code};
+ }
+
+ CurlResponse<std::string> DownloadToFile(
+ const std::string& url, const std::string& path,
+ const std::vector<std::string>& headers) {
+ LOG(INFO) << "Attempting to save \"" << url << "\" to \"" << path << "\"";
+ std::fstream stream;
+ auto callback = [&stream, path](char* data, size_t size) -> bool {
+ if (data == nullptr) {
+ stream.open(path, std::ios::out | std::ios::binary | std::ios::trunc);
+ return !stream.fail();
+ }
+ stream.write(data, size);
+ return !stream.fail();
+ };
+ auto callback_res = DownloadToCallback(callback, url, headers);
+ if (!callback_res.data) {
+ return {"", callback_res.http_code};
+ }
+ return {path, callback_res.http_code};
+ std::lock_guard<std::mutex> lock(mutex_);
+ if (!curl_) {
+ LOG(ERROR) << "curl was not initialized\n";
+ return {"", -1};
+ }
+ }
+
+ CurlResponse<std::string> DownloadToString(
+ const std::string& url, const std::vector<std::string>& headers) {
+ std::stringstream stream;
+ auto callback = [&stream](char* data, size_t size) -> bool {
+ if (data == nullptr) {
+ stream = std::stringstream();
+ return true;
+ }
+ stream.write(data, size);
+ return true;
+ };
+ auto callback_res = DownloadToCallback(callback, url, headers);
+ if (!callback_res.data) {
+ return {"", callback_res.http_code};
+ }
+ return {stream.str(), callback_res.http_code};
+ }
+
+ CurlResponse<Json::Value> DownloadToJson(
+ const std::string& url, const std::vector<std::string>& headers) {
+ CurlResponse<std::string> response = DownloadToString(url, headers);
+ const std::string& contents = response.data;
+ Json::CharReaderBuilder builder;
+ std::unique_ptr<Json::CharReader> reader(builder.newCharReader());
+ Json::Value json;
+ std::string errorMessage;
+ if (!reader->parse(&*contents.begin(), &*contents.end(), &json,
+ &errorMessage)) {
+ LOG(ERROR) << "Could not parse json: " << errorMessage;
+ json["error"] = "Failed to parse json.";
+ json["response"] = contents;
+ }
+ return {json, response.http_code};
+ }
+
+ CurlResponse<Json::Value> DeleteToJson(
+ const std::string& url,
+ const std::vector<std::string>& headers) override {
+ std::lock_guard<std::mutex> lock(mutex_);
+ LOG(INFO) << "Attempting to download \"" << url << "\"";
+ if (!curl_) {
+ LOG(ERROR) << "curl was not initialized\n";
+ return {"", -1};
+ }
+ curl_slist* curl_headers = build_slist(headers);
+ curl_easy_reset(curl_);
+ curl_easy_setopt(curl_, CURLOPT_CUSTOMREQUEST, "DELETE");
+ curl_easy_setopt(curl_, CURLOPT_CAINFO,
+ "/etc/ssl/certs/ca-certificates.crt");
+ curl_easy_setopt(curl_, CURLOPT_HTTPHEADER, curl_headers);
+ curl_easy_setopt(curl_, CURLOPT_URL, url.c_str());
+ std::stringstream data_to_read;
+ curl_easy_setopt(curl_, CURLOPT_WRITEFUNCTION, file_write_callback);
+ curl_easy_setopt(curl_, CURLOPT_WRITEDATA, &data_to_read);
+ char error_buf[CURL_ERROR_SIZE];
+ curl_easy_setopt(curl_, CURLOPT_ERRORBUFFER, error_buf);
+ curl_easy_setopt(curl_, CURLOPT_VERBOSE, 1L);
+ CURLcode res = curl_easy_perform(curl_);
+ if (curl_headers) {
+ curl_slist_free_all(curl_headers);
+ }
+ if (res != CURLE_OK) {
+ LOG(ERROR) << "curl_easy_perform() failed. "
+ << "Code was \"" << res << "\". "
+ << "Strerror was \"" << curl_easy_strerror(res) << "\". "
+ << "Error buffer was \"" << error_buf << "\".";
+ return {"", -1};
+ }
+ long http_code = 0;
+ curl_easy_getinfo(curl_, CURLINFO_RESPONSE_CODE, &http_code);
+
+ auto contents = data_to_read.str();
+ Json::CharReaderBuilder builder;
+ std::unique_ptr<Json::CharReader> reader(builder.newCharReader());
+ Json::Value json;
+ std::string errorMessage;
+ if (!reader->parse(&*contents.begin(), &*contents.end(), &json,
+ &errorMessage)) {
+ LOG(ERROR) << "Could not parse json: " << errorMessage;
+ json["error"] = "Failed to parse json.";
+ json["response"] = contents;
+ }
+ return {json, http_code};
+ }
+
+ std::string UrlEscape(const std::string& text) override {
+ char* escaped_str = curl_easy_escape(curl_, text.c_str(), text.size());
+ std::string ret{escaped_str};
+ curl_free(escaped_str);
+ return ret;
+ }
+
+ private:
+ CURL* curl_;
+ std::mutex mutex_;
+};
+
+class CurlServerErrorRetryingWrapper : public CurlWrapper {
+ public:
+ CurlServerErrorRetryingWrapper(CurlWrapper& inner, int retry_attempts,
+ std::chrono::milliseconds retry_delay)
+ : inner_curl_(inner),
+ retry_attempts_(retry_attempts),
+ retry_delay_(retry_delay) {}
+
+ CurlResponse<std::string> PostToString(
+ const std::string& url, const std::string& data,
+ const std::vector<std::string>& headers) override {
+ return RetryImpl<std::string>(
+ [&, this]() { return inner_curl_.PostToString(url, data, headers); });
+ }
+
+ CurlResponse<Json::Value> PostToJson(
+ const std::string& url, const Json::Value& data,
+ const std::vector<std::string>& headers) override {
+ return RetryImpl<Json::Value>(
+ [&, this]() { return inner_curl_.PostToJson(url, data, headers); });
+ }
+
+ CurlResponse<Json::Value> PostToJson(
+ const std::string& url, const std::string& data,
+ const std::vector<std::string>& headers) override {
+ return RetryImpl<Json::Value>(
+ [&, this]() { return inner_curl_.PostToJson(url, data, headers); });
+ }
+
+ CurlResponse<std::string> DownloadToFile(
+ const std::string& url, const std::string& path,
+ const std::vector<std::string>& headers) {
+ return RetryImpl<std::string>(
+ [&, this]() { return inner_curl_.DownloadToFile(url, path, headers); });
+ }
+
+ CurlResponse<std::string> DownloadToString(
+ const std::string& url, const std::vector<std::string>& headers) {
+ return RetryImpl<std::string>(
+ [&, this]() { return inner_curl_.DownloadToString(url, headers); });
+ }
+
+ CurlResponse<Json::Value> DownloadToJson(
+ const std::string& url, const std::vector<std::string>& headers) {
+ return RetryImpl<Json::Value>(
+ [&, this]() { return inner_curl_.DownloadToJson(url, headers); });
+ }
+
+ CurlResponse<bool> DownloadToCallback(
+ DataCallback cb, const std::string& url,
+ const std::vector<std::string>& hdrs) override {
+ return RetryImpl<bool>(
+ [&, this]() { return inner_curl_.DownloadToCallback(cb, url, hdrs); });
+ }
+ CurlResponse<Json::Value> DeleteToJson(
+ const std::string& url,
+ const std::vector<std::string>& headers) override {
+ return RetryImpl<Json::Value>(
+ [&, this]() { return inner_curl_.DeleteToJson(url, headers); });
+ }
+
+ std::string UrlEscape(const std::string& text) override {
+ return inner_curl_.UrlEscape(text);
+ }
+
+ private:
+ template <typename T>
+ CurlResponse<T> RetryImpl(std::function<CurlResponse<T>()> attempt_fn) {
+ CurlResponse<T> response;
+ for (int attempt = 0; attempt != retry_attempts_; ++attempt) {
+ if (attempt != 0) {
+ std::this_thread::sleep_for(retry_delay_);
+ }
+ response = attempt_fn();
+ if (!response.HttpServerError()) {
+ return response;
+ }
+ }
+ return response;
+ }
+
+ private:
+ CurlWrapper& inner_curl_;
+ int retry_attempts_;
+ std::chrono::milliseconds retry_delay_;
+};
+
+} // namespace
+
+/* static */ std::unique_ptr<CurlWrapper> CurlWrapper::Create() {
+ return std::unique_ptr<CurlWrapper>(new CurlWrapperImpl());
+}
+
+/* static */ std::unique_ptr<CurlWrapper> CurlWrapper::WithServerErrorRetry(
+ CurlWrapper& inner, int retry_attempts,
+ std::chrono::milliseconds retry_delay) {
+ return std::unique_ptr<CurlWrapper>(
+ new CurlServerErrorRetryingWrapper(inner, retry_attempts, retry_delay));
+}
+
+CurlWrapper::~CurlWrapper() = default;
+}
diff --git a/host/libs/web/curl_wrapper.h b/host/libs/web/curl_wrapper.h
new file mode 100644
index 0000000..3e897a4
--- /dev/null
+++ b/host/libs/web/curl_wrapper.h
@@ -0,0 +1,74 @@
+//
+// Copyright (C) 2019 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#pragma once
+
+#include <chrono>
+#include <mutex>
+#include <string>
+
+#include <json/json.h>
+
+namespace cuttlefish {
+
+template <typename T>
+struct CurlResponse {
+ bool HttpInfo() { return http_code >= 100 && http_code <= 199; }
+ bool HttpSuccess() { return http_code >= 200 && http_code <= 299; }
+ bool HttpRedirect() { return http_code >= 300 && http_code <= 399; }
+ bool HttpClientError() { return http_code >= 400 && http_code <= 499; }
+ bool HttpServerError() { return http_code >= 500 && http_code <= 599; }
+
+ T data;
+ long http_code;
+};
+
+class CurlWrapper {
+ public:
+ typedef std::function<bool(char*, size_t)> DataCallback;
+
+ static std::unique_ptr<CurlWrapper> Create();
+ static std::unique_ptr<CurlWrapper> WithServerErrorRetry(
+ CurlWrapper&, int retry_attempts, std::chrono::milliseconds retry_delay);
+ virtual ~CurlWrapper();
+
+ virtual CurlResponse<std::string> PostToString(
+ const std::string& url, const std::string& data,
+ const std::vector<std::string>& headers = {}) = 0;
+ virtual CurlResponse<Json::Value> PostToJson(
+ const std::string& url, const std::string& data,
+ const std::vector<std::string>& headers = {}) = 0;
+ virtual CurlResponse<Json::Value> PostToJson(
+ const std::string& url, const Json::Value& data,
+ const std::vector<std::string>& headers = {}) = 0;
+
+ virtual CurlResponse<std::string> DownloadToFile(
+ const std::string& url, const std::string& path,
+ const std::vector<std::string>& headers = {}) = 0;
+ virtual CurlResponse<std::string> DownloadToString(
+ const std::string& url, const std::vector<std::string>& headers = {}) = 0;
+ virtual CurlResponse<Json::Value> DownloadToJson(
+ const std::string& url, const std::vector<std::string>& headers = {}) = 0;
+ virtual CurlResponse<bool> DownloadToCallback(
+ DataCallback callback, const std::string& url,
+ const std::vector<std::string>& headers = {}) = 0;
+
+ virtual CurlResponse<Json::Value> DeleteToJson(
+ const std::string& url, const std::vector<std::string>& headers = {}) = 0;
+
+ virtual std::string UrlEscape(const std::string&) = 0;
+};
+
+}
diff --git a/host/commands/fetcher/install_zip.cc b/host/libs/web/install_zip.cc
similarity index 100%
rename from host/commands/fetcher/install_zip.cc
rename to host/libs/web/install_zip.cc
diff --git a/host/commands/fetcher/install_zip.h b/host/libs/web/install_zip.h
similarity index 100%
rename from host/commands/fetcher/install_zip.h
rename to host/libs/web/install_zip.h
diff --git a/host/libs/websocket/websocket_handler.cpp b/host/libs/websocket/websocket_handler.cpp
index 9d6f636..c80b150 100644
--- a/host/libs/websocket/websocket_handler.cpp
+++ b/host/libs/websocket/websocket_handler.cpp
@@ -18,8 +18,18 @@
#include <android-base/logging.h>
#include <libwebsockets.h>
+#include "host/libs/websocket/websocket_server.h"
+
namespace cuttlefish {
+namespace {
+void AppendData(const char* data, size_t len, std::string& buffer) {
+ auto ptr = reinterpret_cast<const uint8_t*>(data);
+ buffer.reserve(buffer.size() + len);
+ buffer.insert(buffer.end(), ptr, ptr + len);
+}
+} // namespace
+
WebSocketHandler::WebSocketHandler(struct lws* wsi) : wsi_(wsi) {}
void WebSocketHandler::EnqueueMessage(const uint8_t* data, size_t len,
@@ -34,6 +44,8 @@
// updating the buffer.
void WebSocketHandler::WriteWsBuffer(WebSocketHandler::WsBuffer& ws_buffer) {
auto len = ws_buffer.data.size() - LWS_PRE;
+ // For http2 there must be LWS_PRE bytes at the end as well.
+ ws_buffer.data.resize(ws_buffer.data.size() + LWS_PRE);
auto flags = lws_write_ws_flags(
ws_buffer.binary ? LWS_WRITE_BINARY : LWS_WRITE_TEXT, true, true);
auto res = lws_write(wsi_, &ws_buffer.data[LWS_PRE], len,
@@ -44,7 +56,7 @@
if (res < 0) {
// This shouldn't happen since this function is called in response to a
// LWS_CALLBACK_SERVER_WRITEABLE call.
- LOG(FATAL) << "Failed to write data on the websocket";
+ LOG(ERROR) << "Failed to write data on the websocket";
}
}
@@ -67,4 +79,28 @@
lws_callback_on_writable(wsi_);
}
+DynHandler::DynHandler(struct lws* wsi) : wsi_(wsi), out_buffer_(LWS_PRE, 0) {}
+
+void DynHandler::AppendDataOut(const std::string& data) {
+ AppendData(data.c_str(), data.size(), out_buffer_);
+}
+
+void DynHandler::AppendDataIn(void* data, size_t len) {
+ AppendData(reinterpret_cast<char*>(data), len, in_buffer_);
+}
+
+int DynHandler::OnWritable() {
+ auto len = out_buffer_.size() - LWS_PRE;
+ // For http2 there must be LWS_PRE bytes at the end as well.
+ out_buffer_.resize(out_buffer_.size() + LWS_PRE);
+ auto res = lws_write(wsi_, reinterpret_cast<uint8_t*>(&out_buffer_[LWS_PRE]),
+ len, LWS_WRITE_HTTP_FINAL);
+ if (res != len) {
+ // This shouldn't happen since this function is called in response to a
+ // LWS_CALLBACK_SERVER_WRITEABLE call.
+ LOG(ERROR) << "Failed to write HTTP response";
+ }
+ return lws_http_transaction_completed(wsi_);
+}
+size_t DynHandler::content_len() const { return out_buffer_.size() - LWS_PRE; }
} // namespace cuttlefish
diff --git a/host/libs/websocket/websocket_handler.h b/host/libs/websocket/websocket_handler.h
index 45b51fa..e9a63ca 100644
--- a/host/libs/websocket/websocket_handler.h
+++ b/host/libs/websocket/websocket_handler.h
@@ -16,6 +16,7 @@
#pragma once
#include <deque>
+#include <string>
#include <vector>
struct lws;
@@ -63,4 +64,46 @@
virtual std::shared_ptr<WebSocketHandler> Build(struct lws* wsi) = 0;
};
+class WebSocketServer;
+
+enum class HttpStatusCode : int {
+ // From https://developer.mozilla.org/en-US/docs/Web/HTTP/Status
+ Ok = 200,
+ NoContent = 204,
+ BadRequest = 400,
+ Unauthorized = 401,
+ NotFound = 404,
+ MethodNotAllowed = 405,
+ Conflict = 409,
+};
+
+class DynHandler {
+ public:
+ DynHandler(struct lws* wsi);
+
+ virtual ~DynHandler() = default;
+ // TODO (jemoreira): Allow more than just JSON replies
+ // TODO (jemoreira): Receive request parameters
+ // Handle a GET request.
+ virtual HttpStatusCode DoGet() = 0;
+ // Handle a POST request.
+ virtual HttpStatusCode DoPost() = 0;
+
+ protected:
+ void AppendDataOut(const std::string& data);
+ const std::string& GetDataIn() const { return in_buffer_; }
+
+ private:
+ friend WebSocketServer;
+ void AppendDataIn(void* data, size_t len);
+ int OnWritable();
+ size_t content_len() const;
+
+ struct lws* wsi_;
+ std::string in_buffer_ = {};
+ std::string out_buffer_ = {};
+};
+
+using DynHandlerFactory =
+ std::function<std::unique_ptr<DynHandler>(struct lws*)>;
} // namespace cuttlefish
diff --git a/host/libs/websocket/websocket_server.cpp b/host/libs/websocket/websocket_server.cpp
index 40f1357..f48209b 100644
--- a/host/libs/websocket/websocket_server.cpp
+++ b/host/libs/websocket/websocket_server.cpp
@@ -26,29 +26,158 @@
#include <host/libs/websocket/websocket_handler.h>
namespace cuttlefish {
-WebSocketServer::WebSocketServer(
- const char* protocol_name,
- const std::string &certs_dir,
- const std::string &assets_dir,
- int server_port) {
- std::string cert_file = certs_dir + "/server.crt";
- std::string key_file = certs_dir + "/server.key";
- std::string ca_file = certs_dir + "/CA.crt";
+namespace {
+
+std::string GetPath(struct lws* wsi) {
+ auto len = lws_hdr_total_length(wsi, WSI_TOKEN_GET_URI);
+ std::string path(len + 1, '\0');
+ auto ret = lws_hdr_copy(wsi, path.data(), path.size(), WSI_TOKEN_GET_URI);
+ if (ret <= 0) {
+ len = lws_hdr_total_length(wsi, WSI_TOKEN_HTTP_COLON_PATH);
+ path.resize(len + 1, '\0');
+ ret =
+ lws_hdr_copy(wsi, path.data(), path.size(), WSI_TOKEN_HTTP_COLON_PATH);
+ }
+ if (ret < 0) {
+ LOG(FATAL) << "Something went wrong getting the path";
+ }
+ path.resize(len);
+ return path;
+}
+
+const std::vector<std::pair<std::string, std::string>> kCORSHeaders = {
+ {"Access-Control-Allow-Origin:", "*"},
+ {"Access-Control-Allow-Methods:", "POST, GET, OPTIONS"},
+ {"Access-Control-Allow-Headers:",
+ "Content-Type, Access-Control-Allow-Headers, Authorization, "
+ "X-Requested-With, Accept"}};
+
+bool AddCORSHeaders(struct lws* wsi, unsigned char** buffer_ptr,
+ unsigned char* buffer_end) {
+ for (const auto& header : kCORSHeaders) {
+ const auto& name = header.first;
+ const auto& value = header.second;
+ if (lws_add_http_header_by_name(
+ wsi, reinterpret_cast<const unsigned char*>(name.c_str()),
+ reinterpret_cast<const unsigned char*>(value.c_str()), value.size(),
+ buffer_ptr, buffer_end)) {
+ return false;
+ }
+ }
+ return true;
+}
+
+bool WriteCommonHttpHeaders(int status, const char* mime_type,
+ size_t content_len, struct lws* wsi) {
+ constexpr size_t BUFF_SIZE = 2048;
+ uint8_t header_buffer[LWS_PRE + BUFF_SIZE];
+ const auto start = &header_buffer[LWS_PRE];
+ auto p = &header_buffer[LWS_PRE];
+ auto end = start + BUFF_SIZE;
+ if (lws_add_http_common_headers(wsi, status, mime_type, content_len, &p,
+ end)) {
+ LOG(ERROR) << "Failed to write headers for response";
+ return false;
+ }
+ if (!AddCORSHeaders(wsi, &p, end)) {
+ LOG(ERROR) << "Failed to write CORS headers for response";
+ return false;
+ }
+ if (lws_finalize_write_http_header(wsi, start, &p, end)) {
+ LOG(ERROR) << "Failed to finalize headers for response";
+ return false;
+ }
+ return true;
+}
+
+} // namespace
+WebSocketServer::WebSocketServer(const char* protocol_name,
+ const std::string& assets_dir, int server_port)
+ : WebSocketServer(protocol_name, "", assets_dir, server_port) {}
+
+WebSocketServer::WebSocketServer(const char* protocol_name,
+ const std::string& certs_dir,
+ const std::string& assets_dir, int server_port)
+ : protocol_name_(protocol_name),
+ assets_dir_(assets_dir),
+ certs_dir_(certs_dir),
+ server_port_(server_port) {}
+
+void WebSocketServer::InitializeLwsObjects() {
+ std::string cert_file = certs_dir_ + "/server.crt";
+ std::string key_file = certs_dir_ + "/server.key";
+ std::string ca_file = certs_dir_ + "/CA.crt";
retry_ = {
.secs_since_valid_ping = 3,
.secs_since_valid_hangup = 10,
};
- struct lws_protocols protocols[] = {
- {protocol_name, ServerCallback, 4096, 0, 0, nullptr, 0},
- {nullptr, nullptr, 0, 0, 0, nullptr, 0}};
+ struct lws_protocols protocols[] = //
+ {{
+ .name = protocol_name_.c_str(),
+ .callback = WebsocketCallback,
+ .per_session_data_size = 0,
+ .rx_buffer_size = 0,
+ .id = 0,
+ .user = this,
+ .tx_packet_size = 0,
+ },
+ {
+ .name = "__http_polling__",
+ .callback = DynHttpCallback,
+ .per_session_data_size = 0,
+ .rx_buffer_size = 0,
+ .id = 0,
+ .user = this,
+ .tx_packet_size = 0,
+ },
+ {
+ .name = nullptr,
+ .callback = nullptr,
+ .per_session_data_size = 0,
+ .rx_buffer_size = 0,
+ .id = 0,
+ .user = nullptr,
+ .tx_packet_size = 0,
+ }};
- mount_ = {
- .mount_next = nullptr,
+ dyn_mounts_.reserve(dyn_handler_factories_.size());
+ for (auto& handler_entry : dyn_handler_factories_) {
+ auto& path = handler_entry.first;
+ dyn_mounts_.push_back({
+ .mount_next = nullptr,
+ .mountpoint = path.c_str(),
+ .mountpoint_len = static_cast<uint8_t>(path.size()),
+ .origin = "__http_polling__",
+ .def = nullptr,
+ .protocol = nullptr,
+ .cgienv = nullptr,
+ .extra_mimetypes = nullptr,
+ .interpret = nullptr,
+ .cgi_timeout = 0,
+ .cache_max_age = 0,
+ .auth_mask = 0,
+ .cache_reusable = 0,
+ .cache_revalidate = 0,
+ .cache_intermediaries = 0,
+ .origin_protocol = LWSMPRO_CALLBACK, // dynamic
+ .basic_auth_login_file = nullptr,
+ });
+ }
+ struct lws_http_mount* next_mount = nullptr;
+ // Set up the linked list after all the mounts have been created to ensure
+ // pointers are not invalidated.
+ for (auto& mount : dyn_mounts_) {
+ mount.mount_next = next_mount;
+ next_mount = &mount;
+ }
+
+ static_mount_ = {
+ .mount_next = next_mount,
.mountpoint = "/",
.mountpoint_len = 1,
- .origin = assets_dir.c_str(),
+ .origin = assets_dir_.c_str(),
.def = "index.html",
.protocol = nullptr,
.cgienv = nullptr,
@@ -71,20 +200,22 @@
"font-src https://fonts.gstatic.com/; "};
memset(&info, 0, sizeof info);
- info.port = server_port;
- info.mounts = &mount_;
+ info.port = server_port_;
+ info.mounts = &static_mount_;
info.protocols = protocols;
info.vhost_name = "localhost";
- info.ws_ping_pong_interval = 10;
info.headers = &headers_;
- info.options |= LWS_SERVER_OPTION_DO_SSL_GLOBAL_INIT;
- info.ssl_cert_filepath = cert_file.c_str();
- info.ssl_private_key_filepath = key_file.c_str();
- if (FileExists(ca_file)) {
- info.ssl_ca_filepath = ca_file.c_str();
- }
info.retry_and_idle_policy = &retry_;
+ if (!certs_dir_.empty()) {
+ info.options |= LWS_SERVER_OPTION_DO_SSL_GLOBAL_INIT;
+ info.ssl_cert_filepath = cert_file.c_str();
+ info.ssl_private_key_filepath = key_file.c_str();
+ if (FileExists(ca_file)) {
+ info.ssl_ca_filepath = ca_file.c_str();
+ }
+ }
+
context_ = lws_create_context(&info);
if (!context_) {
LOG(FATAL) << "Failed to create websocket context";
@@ -92,12 +223,19 @@
}
void WebSocketServer::RegisterHandlerFactory(
- const std::string &path,
+ const std::string& path,
std::unique_ptr<WebSocketHandlerFactory> handler_factory_p) {
handler_factories_[path] = std::move(handler_factory_p);
}
+void WebSocketServer::RegisterDynHandlerFactory(
+ const std::string& path,
+ DynHandlerFactory handler_factory) {
+ dyn_handler_factories_[path] = std::move(handler_factory);
+}
+
void WebSocketServer::Serve() {
+ InitializeLwsObjects();
int n = 0;
while (n >= 0) {
n = lws_service(context_, 0);
@@ -105,27 +243,127 @@
lws_context_destroy(context_);
}
-std::unordered_map<struct lws*, std::shared_ptr<WebSocketHandler>> WebSocketServer::handlers_ = {};
-std::unordered_map<std::string, std::unique_ptr<WebSocketHandlerFactory>>
- WebSocketServer::handler_factories_ = {};
-
-std::string WebSocketServer::GetPath(struct lws* wsi) {
- auto len = lws_hdr_total_length(wsi, WSI_TOKEN_GET_URI);
- std::string path(len + 1, '\0');
- auto ret = lws_hdr_copy(wsi, path.data(), path.size(), WSI_TOKEN_GET_URI);
- if (ret <= 0) {
- len = lws_hdr_total_length(wsi, WSI_TOKEN_HTTP_COLON_PATH);
- path.resize(len + 1, '\0');
- ret = lws_hdr_copy(wsi, path.data(), path.size(), WSI_TOKEN_HTTP_COLON_PATH);
+int WebSocketServer::WebsocketCallback(struct lws* wsi,
+ enum lws_callback_reasons reason,
+ void* user, void* in, size_t len) {
+ auto protocol = lws_get_protocol(wsi);
+ if (!protocol) {
+ // Some callback reasons are always handled by the first protocol, before a
+ // wsi struct is even created.
+ return lws_callback_http_dummy(wsi, reason, user, in, len);
}
- if (ret < 0) {
- LOG(FATAL) << "Something went wrong getting the path";
- }
- path.resize(len);
- return path;
+ return reinterpret_cast<WebSocketServer*>(protocol->user)
+ ->ServerCallback(wsi, reason, user, in, len);
}
-int WebSocketServer::ServerCallback(struct lws* wsi, enum lws_callback_reasons reason,
+int WebSocketServer::DynHttpCallback(struct lws* wsi,
+ enum lws_callback_reasons reason,
+ void* user, void* in, size_t len) {
+ auto protocol = lws_get_protocol(wsi);
+ if (!protocol) {
+ LOG(ERROR) << "No protocol associated with connection";
+ return 1;
+ }
+ return reinterpret_cast<WebSocketServer*>(protocol->user)
+ ->DynServerCallback(wsi, reason, user, in, len);
+}
+
+int WebSocketServer::DynServerCallback(struct lws* wsi,
+ enum lws_callback_reasons reason,
+ void* user, void* in, size_t len) {
+ switch (reason) {
+ case LWS_CALLBACK_HTTP: {
+ char* path_raw;
+ int path_len;
+ auto method = lws_http_get_uri_and_method(wsi, &path_raw, &path_len);
+ if (method < 0) {
+ return 1;
+ }
+ std::string path(path_raw, path_len);
+ auto handler = InstantiateDynHandler(path, wsi);
+ if (!handler) {
+ if (!WriteCommonHttpHeaders(static_cast<int>(HttpStatusCode::NotFound),
+ "application/json", 0, wsi)) {
+ return 1;
+ }
+ return lws_http_transaction_completed(wsi);
+ }
+ dyn_handlers_[wsi] = std::move(handler);
+ switch (method) {
+ case LWSHUMETH_GET: {
+ auto status = dyn_handlers_[wsi]->DoGet();
+ if (!WriteCommonHttpHeaders(static_cast<int>(status),
+ "application/json",
+ dyn_handlers_[wsi]->content_len(), wsi)) {
+ return 1;
+ }
+ // Write the response later, when the server is ready
+ lws_callback_on_writable(wsi);
+ break;
+ }
+ case LWSHUMETH_POST:
+ // Do nothing until the body has been read
+ break;
+ case LWSHUMETH_OPTIONS: {
+ // Response for CORS preflight
+ auto status = HttpStatusCode::NoContent;
+ if (!WriteCommonHttpHeaders(static_cast<int>(status), "", 0, wsi)) {
+ return 1;
+ }
+ lws_callback_on_writable(wsi);
+ break;
+ }
+ default:
+ LOG(ERROR) << "Unsupported HTTP method: " << method;
+ return 1;
+ }
+ break;
+ }
+ case LWS_CALLBACK_HTTP_BODY: {
+ auto handler = dyn_handlers_[wsi].get();
+ if (!handler) {
+ LOG(WARNING) << "Received body for unknown wsi";
+ return 1;
+ }
+ handler->AppendDataIn(in, len);
+ break;
+ }
+ case LWS_CALLBACK_HTTP_BODY_COMPLETION: {
+ auto handler = dyn_handlers_[wsi].get();
+ if (!handler) {
+ LOG(WARNING) << "Unexpected body completion event from unknown wsi";
+ return 1;
+ }
+ auto status = handler->DoPost();
+ if (!WriteCommonHttpHeaders(static_cast<int>(status), "application/json",
+ dyn_handlers_[wsi]->content_len(), wsi)) {
+ return 1;
+ }
+ lws_callback_on_writable(wsi);
+ break;
+ }
+ case LWS_CALLBACK_HTTP_WRITEABLE: {
+ auto handler = dyn_handlers_[wsi].get();
+ if (!handler) {
+ LOG(WARNING) << "Unknown wsi became writable";
+ return 1;
+ }
+ auto ret = handler->OnWritable();
+ dyn_handlers_.erase(wsi);
+ // Make sure the connection (in HTTP 1) or stream (in HTTP 2) is closed
+ // after the response is written
+ return ret;
+ }
+ case LWS_CALLBACK_CLOSED_HTTP:
+ break;
+ default:
+ return lws_callback_http_dummy(wsi, reason, user, in, len);
+ }
+ return 0;
+}
+
+int WebSocketServer::ServerCallback(struct lws* wsi,
+ enum lws_callback_reasons reason,
void* user, void* in, size_t len) {
switch (reason) {
case LWS_CALLBACK_ESTABLISHED: {
@@ -170,7 +408,7 @@
handler->OnReceive(reinterpret_cast<const uint8_t*>(in), len,
lws_frame_is_binary(wsi), is_final);
} else {
- LOG(WARNING) << "Unkwnown wsi sent data";
+ LOG(WARNING) << "Unknown wsi sent data";
}
break;
}
@@ -187,9 +425,21 @@
LOG(ERROR) << "Wrong path provided in URI: " << uri_path;
return nullptr;
} else {
- LOG(INFO) << "Creating handler for " << uri_path;
+ LOG(VERBOSE) << "Creating handler for " << uri_path;
return it->second->Build(wsi);
}
}
+std::unique_ptr<DynHandler> WebSocketServer::InstantiateDynHandler(
+ const std::string& uri_path, struct lws* wsi) {
+ auto it = dyn_handler_factories_.find(uri_path);
+ if (it == dyn_handler_factories_.end()) {
+ LOG(ERROR) << "Wrong path provided in URI: " << uri_path;
+ return nullptr;
+ } else {
+ LOG(VERBOSE) << "Creating handler for " << uri_path;
+ return it->second(wsi);
+ }
+}
+
} // namespace cuttlefish
diff --git a/host/libs/websocket/websocket_server.h b/host/libs/websocket/websocket_server.h
index 0695b3b..768578a 100644
--- a/host/libs/websocket/websocket_server.h
+++ b/host/libs/websocket/websocket_server.h
@@ -18,6 +18,7 @@
#include <string>
#include <unordered_map>
+#include <vector>
#include <android-base/logging.h>
#include <libwebsockets.h>
@@ -27,32 +28,62 @@
namespace cuttlefish {
class WebSocketServer {
public:
- WebSocketServer(
- const char* protocol_name,
- const std::string &certs_dir,
- const std::string &assets_dir,
- int port);
+ // Uses HTTP and WS
+ WebSocketServer(const char* protocol_name, const std::string& assets_dir,
+ int port);
+ // Uses HTTPS and WSS when a certificates directory is provided
+ WebSocketServer(const char* protocol_name, const std::string& certs_dir,
+ const std::string& assets_dir, int port);
~WebSocketServer() = default;
+ // Register a handler factory for websocket connections. A new handler will be
+ // created for each new websocket connection.
void RegisterHandlerFactory(
- const std::string &path,
- std::unique_ptr<WebSocketHandlerFactory> handler_factory_p);
+ const std::string& path,
+ std::unique_ptr<WebSocketHandlerFactory> handler_factory_p);
+
+ // Register a handler factory for dynamic HTTP requests. A new handler will be
+ // created for each HTTP request.
+ void RegisterDynHandlerFactory(const std::string& path,
+ DynHandlerFactory handler_factory);
+
void Serve();
-
private:
- static std::unordered_map<struct lws*, std::shared_ptr<WebSocketHandler>> handlers_;
- static std::unordered_map<std::string, std::unique_ptr<WebSocketHandlerFactory>>
- handler_factories_;
+ static int WebsocketCallback(struct lws* wsi,
+ enum lws_callback_reasons reason, void* user,
+ void* in, size_t len);
- static std::string GetPath(struct lws* wsi);
- static int ServerCallback(struct lws* wsi, enum lws_callback_reasons reason,
+ static int DynHttpCallback(struct lws* wsi, enum lws_callback_reasons reason,
+ void* user, void* in, size_t len);
+
+ int ServerCallback(struct lws* wsi, enum lws_callback_reasons reason,
void* user, void* in, size_t len);
- static std::shared_ptr<WebSocketHandler> InstantiateHandler(
+ int DynServerCallback(struct lws* wsi,
+ enum lws_callback_reasons reason, void* user,
+ void* in, size_t len);
+ std::shared_ptr<WebSocketHandler> InstantiateHandler(
+ const std::string& uri_path, struct lws* wsi);
+ std::unique_ptr<DynHandler> InstantiateDynHandler(
const std::string& uri_path, struct lws* wsi);
+ void InitializeLwsObjects();
+
+ std::unordered_map<struct lws*, std::shared_ptr<WebSocketHandler>> handlers_ =
+ {};
+ std::unordered_map<std::string, std::unique_ptr<WebSocketHandlerFactory>>
+ handler_factories_ = {};
+ std::unordered_map<struct lws*, std::unique_ptr<DynHandler>> dyn_handlers_ =
+ {};
+ std::unordered_map<std::string, DynHandlerFactory> dyn_handler_factories_ =
+ {};
+ std::string protocol_name_;
+ std::string assets_dir_;
+ std::string certs_dir_;
+ int server_port_;
struct lws_context* context_;
- struct lws_http_mount mount_;
+ struct lws_http_mount static_mount_;
+ std::vector<struct lws_http_mount> dyn_mounts_ = {};
struct lws_protocol_vhost_options headers_;
lws_retry_bo_t retry_;
};
diff --git a/host/commands/mk_cdisk/Android.bp b/host/libs/wmediumd_controller/Android.bp
similarity index 77%
copy from host/commands/mk_cdisk/Android.bp
copy to host/libs/wmediumd_controller/Android.bp
index a0cf8ba..1343df2 100644
--- a/host/commands/mk_cdisk/Android.bp
+++ b/host/libs/wmediumd_controller/Android.bp
@@ -17,25 +17,24 @@
default_applicable_licenses: ["Android-Apache-2.0"],
}
-cc_binary {
- name: "mk_cdisk",
+cc_library_static {
+ name: "libcuttlefish_wmediumd_controller",
srcs: [
- "mk_cdisk.cc",
+ "wmediumd_api_protocol.cpp",
+ "wmediumd_controller.cpp",
+ ],
+ export_include_dirs: [
+ ".",
+ ],
+ header_libs: [
+ "wmediumd_headers",
],
shared_libs: [
+ "libbase",
"libcuttlefish_fs",
"libcuttlefish_utils",
- "libbase",
- "libjsoncpp",
- "liblog",
- "libz",
],
static_libs: [
- "libcdisk_spec",
- "libext2_uuid",
- "libimage_aggregator",
- "libprotobuf-cpp-lite",
- "libsparse",
],
defaults: ["cuttlefish_host"],
}
diff --git a/host/libs/wmediumd_controller/wmediumd_api_protocol.cpp b/host/libs/wmediumd_controller/wmediumd_api_protocol.cpp
new file mode 100644
index 0000000..61ce771
--- /dev/null
+++ b/host/libs/wmediumd_controller/wmediumd_api_protocol.cpp
@@ -0,0 +1,142 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "wmediumd_api_protocol.h"
+
+#include <android-base/logging.h>
+#include <android-base/strings.h>
+
+#include <cstdlib>
+#include <iostream>
+#include <string>
+#include <vector>
+
+#include "common/libs/fs/shared_buf.h"
+
+template <class T>
+static void AppendBinaryRepresentation(std::string& buf, const T& data) {
+ std::copy(reinterpret_cast<const char*>(&data),
+ reinterpret_cast<const char*>(&data) + sizeof(T),
+ std::back_inserter(buf));
+}
+
+namespace cuttlefish {
+
+std::string WmediumdMessage::Serialize(void) const {
+ std::string result;
+
+ AppendBinaryRepresentation(result, this->Type());
+
+ std::string body;
+ this->SerializeBody(body);
+
+ AppendBinaryRepresentation(result, static_cast<uint32_t>(body.size()));
+
+ std::copy(std::begin(body), std::end(body), std::back_inserter(result));
+
+ return result;
+}
+
+void WmediumdMessageSetControl::SerializeBody(std::string& buf) const {
+ AppendBinaryRepresentation(buf, flags_);
+}
+
+WmediumdMessageSetSnr::WmediumdMessageSetSnr(const std::string& node1,
+ const std::string& node2,
+ uint8_t snr) {
+ auto splitted_mac1 = android::base::Split(node1, ":");
+ auto splitted_mac2 = android::base::Split(node2, ":");
+
+ if (splitted_mac1.size() != 6) {
+ LOG(FATAL) << "invalid mac address length " << node1;
+ }
+
+ if (splitted_mac2.size() != 6) {
+ LOG(FATAL) << "invalid mac address length " << node2;
+ }
+
+ for (int i = 0; i < 6; i++) {
+ char* end_ptr;
+ node1_mac_[i] = (uint8_t)strtol(splitted_mac1[i].c_str(), &end_ptr, 16);
+ if (end_ptr != splitted_mac1[i].c_str() + splitted_mac1[i].size()) {
+ LOG(FATAL) << "cannot parse " << splitted_mac1[i] << " of " << node1;
+ }
+
+ node2_mac_[i] = (uint8_t)strtol(splitted_mac2[i].c_str(), &end_ptr, 16);
+ if (end_ptr != splitted_mac2[i].c_str() + splitted_mac2[i].size()) {
+ LOG(FATAL) << "cannot parse " << splitted_mac2[i] << " of " << node1;
+ }
+ }
+
+ snr_ = snr;
+}
+
+void WmediumdMessageSetSnr::SerializeBody(std::string& buf) const {
+ std::copy(std::begin(node1_mac_), std::end(node1_mac_),
+ std::back_inserter(buf));
+ std::copy(std::begin(node2_mac_), std::end(node2_mac_),
+ std::back_inserter(buf));
+ buf.push_back(snr_);
+}
+
+void WmediumdMessageReloadConfig::SerializeBody(std::string& buf) const {
+ std::copy(std::begin(config_path_), std::end(config_path_),
+ std::back_inserter(buf));
+ buf.push_back('\0');
+}
+
+void WmediumdMessageStartPcap::SerializeBody(std::string& buf) const {
+ std::copy(std::begin(pcap_path_), std::end(pcap_path_),
+ std::back_inserter(buf));
+ buf.push_back('\0');
+}
+
+std::optional<WmediumdMessageStationsList> WmediumdMessageStationsList::Parse(
+ const WmediumdMessageReply& reply) {
+ size_t pos = 0;
+ size_t dataSize = reply.Size();
+ auto data = reply.Data();
+
+ if (reply.Type() != WmediumdMessageType::kStationsList) {
+ LOG(FATAL) << "expected reply type "
+ << static_cast<uint32_t>(WmediumdMessageType::kStationsList)
+ << ", got " << static_cast<uint32_t>(reply.Type()) << std::endl;
+ }
+
+ WmediumdMessageStationsList result;
+
+ if (pos + sizeof(uint32_t) > dataSize) {
+ LOG(ERROR) << "invalid response size";
+ return std::nullopt;
+ }
+
+ uint32_t count = *reinterpret_cast<const uint32_t*>(data + pos);
+ pos += sizeof(uint32_t);
+
+ for (uint32_t i = 0; i < count; ++i) {
+ if (pos + sizeof(wmediumd_station_info) > dataSize) {
+ LOG(ERROR) << "invalid response size";
+ return std::nullopt;
+ }
+ result.station_list_.push_back(
+ *reinterpret_cast<const wmediumd_station_info*>(data + pos));
+ pos += sizeof(wmediumd_station_info);
+ }
+
+ return result;
+}
+
+} // namespace cuttlefish
diff --git a/host/libs/wmediumd_controller/wmediumd_api_protocol.h b/host/libs/wmediumd_controller/wmediumd_api_protocol.h
new file mode 100644
index 0000000..b5cedd6
--- /dev/null
+++ b/host/libs/wmediumd_controller/wmediumd_api_protocol.h
@@ -0,0 +1,180 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <cstdint>
+#include <memory>
+#include <optional>
+#include <string>
+
+#include <wmediumd/api.h>
+
+#include "common/libs/fs/shared_fd.h"
+
+namespace cuttlefish {
+
+enum class WmediumdMessageType : uint32_t {
+ kInvalid = 0,
+ kAck = 1,
+ kRegister = 2,
+ kUnregister = 3,
+ kNetlink = 4,
+ kSetControl = 5,
+ kTxStart = 6,
+ kGetStations = 7,
+ kSetSnr = 8,
+ kReloadConfig = 9,
+ kReloadCurrentConfig = 10,
+ kStartPcap = 11,
+ kStopPcap = 12,
+ kStationsList = 13,
+};
+
+class WmediumdMessage {
+ public:
+ virtual ~WmediumdMessage() {}
+
+ std::string Serialize(void) const;
+
+ virtual WmediumdMessageType Type() const = 0;
+
+ private:
+ virtual void SerializeBody(std::string&) const {};
+};
+
+class WmediumdMessageSetControl : public WmediumdMessage {
+ public:
+ WmediumdMessageSetControl(uint32_t flags) : flags_(flags) {}
+
+ WmediumdMessageType Type() const override {
+ return WmediumdMessageType::kSetControl;
+ }
+
+ private:
+ void SerializeBody(std::string& out) const override;
+ uint32_t flags_;
+};
+
+class WmediumdMessageSetSnr : public WmediumdMessage {
+ public:
+ WmediumdMessageSetSnr(const std::string& node1, const std::string& node2,
+ uint8_t snr);
+
+ WmediumdMessageType Type() const override {
+ return WmediumdMessageType::kSetSnr;
+ }
+
+ private:
+ void SerializeBody(std::string& out) const override;
+
+ uint8_t node1_mac_[6];
+ uint8_t node2_mac_[6];
+ uint8_t snr_;
+};
+
+class WmediumdMessageReloadConfig : public WmediumdMessage {
+ public:
+ WmediumdMessageReloadConfig(const std::string& configPath)
+ : config_path_(configPath) {}
+
+ WmediumdMessageType Type() const override {
+ return WmediumdMessageType::kReloadConfig;
+ }
+
+ private:
+ void SerializeBody(std::string& out) const override;
+
+ std::string config_path_;
+};
+
+class WmediumdMessageReloadCurrentConfig : public WmediumdMessage {
+ public:
+ WmediumdMessageReloadCurrentConfig() = default;
+
+ WmediumdMessageType Type() const override {
+ return WmediumdMessageType::kReloadCurrentConfig;
+ }
+};
+
+class WmediumdMessageStartPcap : public WmediumdMessage {
+ public:
+ WmediumdMessageStartPcap(const std::string& pcapPath)
+ : pcap_path_(pcapPath) {}
+
+ WmediumdMessageType Type() const override {
+ return WmediumdMessageType::kStartPcap;
+ }
+
+ private:
+ void SerializeBody(std::string& out) const override;
+
+ std::string pcap_path_;
+};
+
+class WmediumdMessageStopPcap : public WmediumdMessage {
+ public:
+ WmediumdMessageStopPcap() = default;
+
+ WmediumdMessageType Type() const override {
+ return WmediumdMessageType::kStopPcap;
+ }
+};
+
+class WmediumdMessageGetStations : public WmediumdMessage {
+ public:
+ WmediumdMessageGetStations() = default;
+
+ WmediumdMessageType Type() const override {
+ return WmediumdMessageType::kGetStations;
+ }
+};
+
+class WmediumdMessageReply : public WmediumdMessage {
+ public:
+ WmediumdMessageReply() = default;
+ WmediumdMessageReply(WmediumdMessageType type, const std::string& data)
+ : type_(type), data_(data) {}
+
+ WmediumdMessageType Type() const override { return type_; }
+
+ size_t Size() const { return data_.size(); }
+ const char* Data() const { return data_.data(); }
+
+ private:
+ WmediumdMessageType type_;
+ std::string data_;
+};
+
+class WmediumdMessageStationsList : public WmediumdMessage {
+ public:
+ WmediumdMessageStationsList() = default;
+ static std::optional<WmediumdMessageStationsList> Parse(
+ const WmediumdMessageReply& reply);
+
+ WmediumdMessageType Type() const override {
+ return WmediumdMessageType::kStationsList;
+ }
+
+ const std::vector<wmediumd_station_info>& GetStations() const {
+ return station_list_;
+ }
+
+ private:
+ std::vector<wmediumd_station_info> station_list_;
+};
+
+} // namespace cuttlefish
diff --git a/host/libs/wmediumd_controller/wmediumd_controller.cpp b/host/libs/wmediumd_controller/wmediumd_controller.cpp
new file mode 100644
index 0000000..a1e2d10
--- /dev/null
+++ b/host/libs/wmediumd_controller/wmediumd_controller.cpp
@@ -0,0 +1,138 @@
+//
+// Copyright (C) 2021 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "wmediumd_controller.h"
+
+#include <android-base/logging.h>
+#include <sys/socket.h>
+
+#include <cstdint>
+#include <memory>
+#include <optional>
+#include <string>
+
+#include "common/libs/fs/shared_buf.h"
+
+#include "host/libs/wmediumd_controller/wmediumd_api_protocol.h"
+
+namespace cuttlefish {
+
+std::unique_ptr<WmediumdController> WmediumdController::New(
+ const std::string& serverSocketPath) {
+ std::unique_ptr<WmediumdController> result(new WmediumdController);
+
+ if (!result->Connect(serverSocketPath)) {
+ return nullptr;
+ }
+
+ return result;
+}
+
+bool WmediumdController::Connect(const std::string& serverSocketPath) {
+ wmediumd_socket_ =
+ SharedFD::SocketLocalClient(serverSocketPath, false, SOCK_STREAM);
+
+ if (!wmediumd_socket_->IsOpen()) {
+ LOG(ERROR) << "Cannot connect wmediumd control socket " << serverSocketPath
+ << ": " << wmediumd_socket_->StrError();
+ return false;
+ }
+
+ return SetControl(0);
+}
+
+bool WmediumdController::SetSnr(const std::string& node1,
+ const std::string& node2, uint8_t snr) {
+ return SendMessage(WmediumdMessageSetSnr(node1, node2, snr));
+}
+
+bool WmediumdController::SetControl(const uint32_t flags) {
+ return SendMessage(WmediumdMessageSetControl(flags));
+}
+
+bool WmediumdController::ReloadCurrentConfig(void) {
+ return SendMessage(WmediumdMessageReloadCurrentConfig());
+}
+
+bool WmediumdController::ReloadConfig(const std::string& configPath) {
+ return SendMessage(WmediumdMessageReloadConfig(configPath));
+}
+
+bool WmediumdController::StartPcap(const std::string& pcapPath) {
+ return SendMessage(WmediumdMessageStartPcap(pcapPath));
+}
+
+bool WmediumdController::StopPcap(void) {
+ return SendMessage(WmediumdMessageStopPcap());
+}
+
+std::optional<WmediumdMessageStationsList> WmediumdController::GetStations(
+ void) {
+ auto reply = SendMessageWithReply(WmediumdMessageGetStations());
+
+ if (!reply) {
+ return std::nullopt;
+ }
+
+ return WmediumdMessageStationsList::Parse(*reply);
+}
+
+bool WmediumdController::SendMessage(const WmediumdMessage& message) {
+ auto reply = SendMessageWithReply(message);
+
+ if (!reply) {
+ return false;
+ }
+
+ if (reply->Type() != WmediumdMessageType::kAck) {
+ return false;
+ }
+
+ return true;
+}
+
+std::optional<WmediumdMessageReply> WmediumdController::SendMessageWithReply(
+ const WmediumdMessage& message) {
+ auto sendResult = SendAll(wmediumd_socket_, message.Serialize());
+
+ if (!sendResult) {
+ LOG(ERROR) << "sendmessage failed: " << wmediumd_socket_->StrError();
+ return std::nullopt;
+ }
+
+ std::string recvHeader = RecvAll(wmediumd_socket_, sizeof(uint32_t) * 2);
+
+ if (recvHeader.size() != sizeof(uint32_t) * 2) {
+ LOG(ERROR)
+ << "error: RecvAll failed while receiving result header from server";
+ return std::nullopt;
+ }
+
+ uint32_t type = *reinterpret_cast<const uint32_t*>(recvHeader.c_str());
+ uint32_t dataLen =
+ *reinterpret_cast<const uint32_t*>(recvHeader.c_str() + sizeof(uint32_t));
+
+ std::string recvData = RecvAll(wmediumd_socket_, dataLen);
+
+ if (recvData.size() != dataLen) {
+ LOG(ERROR)
+ << "error: RecvAll failed while receiving result data from server";
+ return std::nullopt;
+ }
+
+ return WmediumdMessageReply(static_cast<WmediumdMessageType>(type), recvData);
+}
+
+} // namespace cuttlefish
diff --git a/host/libs/wmediumd_controller/wmediumd_controller.h b/host/libs/wmediumd_controller/wmediumd_controller.h
new file mode 100644
index 0000000..10fd577
--- /dev/null
+++ b/host/libs/wmediumd_controller/wmediumd_controller.h
@@ -0,0 +1,59 @@
+//
+// Copyright (C) 2021 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#pragma once
+
+#include <cstdint>
+#include <memory>
+#include <string>
+
+#include "common/libs/fs/shared_fd.h"
+#include "host/libs/wmediumd_controller/wmediumd_api_protocol.h"
+
+namespace cuttlefish {
+
+class WmediumdController {
+ public:
+ static std::unique_ptr<WmediumdController> New(
+ const std::string& serverSocketPath);
+
+ virtual ~WmediumdController() {}
+
+ WmediumdController(const WmediumdController& rhs) = delete;
+ WmediumdController& operator=(const WmediumdController& rhs) = delete;
+
+ WmediumdController(WmediumdController&& rhs) = delete;
+ WmediumdController& operator=(WmediumdController&& rhs) = delete;
+
+ bool SetControl(const uint32_t flags);
+ bool SetSnr(const std::string& node1, const std::string& node2, uint8_t snr);
+ bool ReloadCurrentConfig(void);
+ bool ReloadConfig(const std::string& configPath);
+ bool StartPcap(const std::string& pcapPath);
+ bool StopPcap(void);
+ std::optional<WmediumdMessageStationsList> GetStations(void);
+
+ private:
+ WmediumdController() {}
+
+ bool Connect(const std::string& serverSocketPath);
+ bool SendMessage(const WmediumdMessage& message);
+ std::optional<WmediumdMessageReply> SendMessageWithReply(
+ const WmediumdMessage& message);
+
+ SharedFD wmediumd_socket_;
+};
+
+} // namespace cuttlefish
diff --git a/host_package.mk b/host_package.mk
index bd6b51f..e1afedf 100644
--- a/host_package.mk
+++ b/host_package.mk
@@ -1,6 +1,6 @@
-cvd_host_packages := $(SOONG_HOST_OUT)/cvd-host_package.tar.gz
+cvd_host_packages := $(HOST_OUT)/cvd-host_package.tar.gz
ifeq ($(HOST_CROSS_OS)_$(HOST_CROSS_ARCH),linux_bionic_arm64)
- cvd_host_packages += $(SOONG_OUT_DIR)/host/$(HOST_CROSS_OS)-$(HOST_CROSS_ARCH)/cvd-host_package.tar.gz
+ cvd_host_packages += $(OUT_DIR)/host/$(HOST_CROSS_OS)-$(HOST_CROSS_ARCH)/cvd-host_package.tar.gz
endif
.PHONY: hosttar
diff --git a/iwyu.imp b/iwyu.imp
new file mode 100644
index 0000000..0c618ca
--- /dev/null
+++ b/iwyu.imp
@@ -0,0 +1,16 @@
+[
+ { symbol: ["std::basic_istream<>::pos_type", "private", "<fstream>", "public"] },
+ { include: ["\"__hash_table\"", "private", "<unordered_map>", "public"] },
+ { include: ["\"__mutex_base\"", "private", "<mutex>", "public"] },
+ { include: ["\"__string\"", "private", "<string>", "public"] },
+ { include: ["\"json/reader.h\"", "private", "<json/json.h>", "public"] },
+ { include: ["\"json/value.h\"", "private", "<json/json.h>", "public"] },
+ { include: ["\"json/writer.h\"", "private", "<json/json.h>", "public"] },
+ { symbol: ["std::forward", "private", "<utility>", "public" ] },
+ { symbol: ["std::ifstream", "private", "<fstream>", "public" ] },
+ { symbol: ["std::less", "private", "<functional>", "public" ] },
+ { symbol: ["std::move", "private", "<utility>", "public"] },
+ { symbol: ["std::ostream", "private", "<ostream>", "public"] },
+ { symbol: ["std::string", "private", "<string>", "public"] },
+ { symbol: ["std::stringstream", "private", "<sstream>", "public"] },
+]
diff --git a/multiarch-howto.md b/multiarch-howto.md
new file mode 100644
index 0000000..fe24a66
--- /dev/null
+++ b/multiarch-howto.md
@@ -0,0 +1,57 @@
+# Adjusting APT Sources for Multiarch
+
+The Cuttlefish host Debian packages can also be built and used on an `arm64`
+based system. However, because certain parts of it are still `amd64`, the
+APT sources of the system need to be adjusted for multiarch so that package
+dependencies can be correctly looked up and installed.
+
+For detailed context, see [Multiarch HOWTO](https://wiki.debian.org/Multiarch/HOWTO), and this document will use Ubuntu 21.04 (Hirsute) as an example for
+making such adjustments.
+
+The basic idea is to first limit the existing APT sources to `arm64` only,
+so that when a new architecture like `amd64` is added, APT won't try to
+fetch packages for the new architecture from the existing repository, as
+`arm64` packages are in "ports", while `amd64` ones are in the main
+repository. So a line in `/etc/apt/sources.list` such as:
+
+```
+deb http://ports.ubuntu.com/ubuntu-ports hirsute main restricted
+```
+
+would be changed to:
+
+```
+deb [arch=arm64] http://ports.ubuntu.com/ubuntu-ports hirsute main restricted
+```
+
+Next, each line of config like the above will be duplicated and modified into
+an entry that corresponds to what's in the main repository, with its
+architecture limited to `amd64`. For example, for the same line as shown above,
+a new entry will be added like this:
+
+```
+deb [arch=amd64] http://archive.ubuntu.com/ubuntu hirsute main restricted
+```
+
+The script below might be handy for this task:
+```bash
+#!/bin/bash
+cp /etc/apt/sources.list ~/sources.list.bak
+(
+ (grep ^deb /etc/apt/sources.list | sed 's/deb /deb [arch=arm64] /') && \
+ (grep ^deb /etc/apt/sources.list | sed 's/deb /deb [arch=amd64] /g; s/ports\.ubuntu/archive.ubuntu/g; s/ubuntu-ports/ubuntu/g') \
+) | tee /tmp/sources.list
+mv /tmp/sources.list /etc/apt/sources.list
+```
+**Note:** please run the above script as `root`, and adjust for differences in
+Ubuntu releases or location prefixed repositories for faster download (e.g.
+`us.archive.ubuntu.com` instead of `archive.ubuntu.com`).
+
+Finally, add the new architecture and do an APT update with:
+```bash
+sudo dpkg --add-architecture amd64
+sudo apt update
+```
+Make sure there's no errors or warnings in the output of `apt update`. To
+restore the previous APT sources list, use the backup file `sources.list.bak`
+saved by the script in your home directory.
diff --git a/recovery/recovery_ui.cpp b/recovery/recovery_ui.cpp
index 217542e..f07e300 100644
--- a/recovery/recovery_ui.cpp
+++ b/recovery/recovery_ui.cpp
@@ -25,5 +25,5 @@
};
Device* make_device() {
- return new EthernetDevice(new CuttlefishRecoveryUI);
+ return new EthernetDevice(new CuttlefishRecoveryUI, "eth1");
}
diff --git a/required_images b/required_images
index 6f5c180..e6f0616 100644
--- a/required_images
+++ b/required_images
@@ -1,4 +1,5 @@
boot.img
+init_boot.img
bootloader
super.img
userdata.img
diff --git a/shared/BoardConfig.mk b/shared/BoardConfig.mk
index 6be1730..b59b39c 100644
--- a/shared/BoardConfig.mk
+++ b/shared/BoardConfig.mk
@@ -27,7 +27,7 @@
BOARD_SYSTEMIMAGE_FILE_SYSTEM_TYPE := $(TARGET_RO_FILE_SYSTEM_TYPE)
-# Boot partition size: 32M
+# Boot partition size: 64M
# This is only used for OTA update packages. The image size on disk
# will not change (as is it not a filesystem.)
BOARD_BOOTIMAGE_PARTITION_SIZE := 67108864
@@ -36,6 +36,11 @@
endif
BOARD_VENDOR_BOOTIMAGE_PARTITION_SIZE := 67108864
+# init_boot partition size is recommended to be 8MB, it can be larger.
+# When this variable is set, init_boot.img will be built with the generic
+# ramdisk, and that ramdisk will no longer be included in boot.img.
+BOARD_INIT_BOOT_IMAGE_PARTITION_SIZE := 8388608
+
# Build a separate vendor.img partition
BOARD_USES_VENDORIMAGE := true
BOARD_VENDORIMAGE_FILE_SYSTEM_TYPE := $(TARGET_RO_FILE_SYSTEM_TYPE)
@@ -58,30 +63,43 @@
# Build a separate vendor_dlkm partition
BOARD_USES_VENDOR_DLKMIMAGE := true
-BOARD_VENDOR_DLKMIMAGE_FILE_SYSTEM_TYPE := ext4
+BOARD_VENDOR_DLKMIMAGE_FILE_SYSTEM_TYPE := $(TARGET_RO_FILE_SYSTEM_TYPE)
TARGET_COPY_OUT_VENDOR_DLKM := vendor_dlkm
# Build a separate odm_dlkm partition
BOARD_USES_ODM_DLKMIMAGE := true
-BOARD_ODM_DLKMIMAGE_FILE_SYSTEM_TYPE := ext4
+BOARD_ODM_DLKMIMAGE_FILE_SYSTEM_TYPE := $(TARGET_RO_FILE_SYSTEM_TYPE)
TARGET_COPY_OUT_ODM_DLKM := odm_dlkm
-# FIXME: Remove this once we generate the vbmeta digest correctly
-BOARD_AVB_MAKE_VBMETA_IMAGE_ARGS += --flag 2
+# Build a separate system_dlkm partition
+BOARD_USES_SYSTEM_DLKMIMAGE := true
+BOARD_SYSTEM_DLKMIMAGE_FILE_SYSTEM_TYPE := $(TARGET_RO_FILE_SYSTEM_TYPE)
+TARGET_COPY_OUT_SYSTEM_DLKM := system_dlkm
+
+# Enable AVB
+BOARD_AVB_ENABLE := true
+BOARD_AVB_ALGORITHM := SHA256_RSA4096
+BOARD_AVB_KEY_PATH := external/avb/test/data/testkey_rsa4096.pem
# Enable chained vbmeta for system image mixing
BOARD_AVB_VBMETA_SYSTEM := product system system_ext
-BOARD_AVB_VBMETA_SYSTEM_KEY_PATH := external/avb/test/data/testkey_rsa2048.pem
-BOARD_AVB_VBMETA_SYSTEM_ALGORITHM := SHA256_RSA2048
+BOARD_AVB_VBMETA_SYSTEM_KEY_PATH := external/avb/test/data/testkey_rsa4096.pem
+BOARD_AVB_VBMETA_SYSTEM_ALGORITHM := SHA256_RSA4096
BOARD_AVB_VBMETA_SYSTEM_ROLLBACK_INDEX := $(PLATFORM_SECURITY_PATCH_TIMESTAMP)
BOARD_AVB_VBMETA_SYSTEM_ROLLBACK_INDEX_LOCATION := 1
# Enable chained vbmeta for boot images
-BOARD_AVB_BOOT_KEY_PATH := external/avb/test/data/testkey_rsa2048.pem
-BOARD_AVB_BOOT_ALGORITHM := SHA256_RSA2048
+BOARD_AVB_BOOT_KEY_PATH := external/avb/test/data/testkey_rsa4096.pem
+BOARD_AVB_BOOT_ALGORITHM := SHA256_RSA4096
BOARD_AVB_BOOT_ROLLBACK_INDEX := $(PLATFORM_SECURITY_PATCH_TIMESTAMP)
BOARD_AVB_BOOT_ROLLBACK_INDEX_LOCATION := 2
+# Enable chained vbmeta for init_boot images
+BOARD_AVB_INIT_BOOT_KEY_PATH := external/avb/test/data/testkey_rsa4096.pem
+BOARD_AVB_INIT_BOOT_ALGORITHM := SHA256_RSA4096
+BOARD_AVB_INIT_BOOT_ROLLBACK_INDEX := $(PLATFORM_SECURITY_PATCH_TIMESTAMP)
+BOARD_AVB_INIT_BOOT_ROLLBACK_INDEX_LOCATION := 3
+
# Using sha256 for dm-verity partitions. b/178983355
# system, system_other, product.
TARGET_AVB_SYSTEM_HASHTREE_ALGORITHM ?= sha256
@@ -100,9 +118,10 @@
BOARD_AVB_VENDOR_ADD_HASHTREE_FOOTER_ARGS += --hash_algorithm sha256
BOARD_AVB_ODM_ADD_HASHTREE_FOOTER_ARGS += --hash_algorithm sha256
-# vendor_dlkm and odm_dlkm.
+# vendor_dlkm, odm_dlkm, and system_dlkm.
BOARD_AVB_VENDOR_DLKM_ADD_HASHTREE_FOOTER_ARGS += --hash_algorithm sha256
BOARD_AVB_ODM_DLKM_ADD_HASHTREE_FOOTER_ARGS += --hash_algorithm sha256
+BOARD_AVB_SYSTEM_DLKM_ADD_HASHTREE_FOOTER_ARGS += --hash_algorithm sha256
BOARD_USES_GENERIC_AUDIO := false
USE_CAMERA_STUB := true
@@ -144,7 +163,14 @@
USE_OPENGL_RENDERER := true
# Wifi.
+ifeq ($(PRODUCT_ENFORCE_MAC80211_HWSIM),true)
+BOARD_WLAN_DEVICE := emulator
+BOARD_HOSTAPD_PRIVATE_LIB := lib_driver_cmd_simulated_cf
+WIFI_HIDL_FEATURE_DUAL_INTERFACE := true
+WIFI_HAL_INTERFACE_COMBINATIONS := {{{STA}, 1}, {{AP}, 1}, {{P2P}, 1}}
+else
BOARD_WLAN_DEVICE := wlan0
+endif
BOARD_HOSTAPD_DRIVER := NL80211
BOARD_WPA_SUPPLICANT_DRIVER := NL80211
BOARD_WPA_SUPPLICANT_PRIVATE_LIB := lib_driver_cmd_simulated_cf
@@ -182,7 +208,7 @@
BOARD_SUPER_PARTITION_SIZE := 7516192768 # 7GiB
BOARD_SUPER_PARTITION_GROUPS := google_system_dynamic_partitions google_vendor_dynamic_partitions
-BOARD_GOOGLE_SYSTEM_DYNAMIC_PARTITIONS_PARTITION_LIST := product system system_ext
+BOARD_GOOGLE_SYSTEM_DYNAMIC_PARTITIONS_PARTITION_LIST := product system system_ext system_dlkm
BOARD_GOOGLE_SYSTEM_DYNAMIC_PARTITIONS_SIZE := 5771362304 # 5.375GiB
BOARD_GOOGLE_VENDOR_DYNAMIC_PARTITIONS_PARTITION_LIST := odm vendor vendor_dlkm odm_dlkm
# 1404MiB, reserve 4MiB for dynamic partition metadata
@@ -200,15 +226,28 @@
# To see full logs from init, disable ratelimiting.
# The default is 5 messages per second amortized, with a burst of up to 10.
BOARD_KERNEL_CMDLINE += printk.devkmsg=on
+
+# Print audit messages for all security check failures
+BOARD_KERNEL_CMDLINE += audit=1
+
+# Reboot immediately on panic
+BOARD_KERNEL_CMDLINE += panic=-1
+
+# Always enable one legacy serial port, for alternative earlycon, kgdb, and
+# serial console. Doesn't do anything on ARM/ARM64 + QEMU or Gem5.
+BOARD_KERNEL_CMDLINE += 8250.nr_uarts=1
+
+# Cuttlefish doesn't use CMA, so don't reserve RAM for it
+BOARD_KERNEL_CMDLINE += cma=0
+
+# Default firmware load path
BOARD_KERNEL_CMDLINE += firmware_class.path=/vendor/etc/
-BOARD_KERNEL_CMDLINE += init=/init
-BOARD_BOOTCONFIG += androidboot.hardware=cutf_cvm
-
-# TODO(b/179489292): Remove once kfence is enabled everywhere
-BOARD_KERNEL_CMDLINE += kfence.sample_interval=500
-
+# Needed to boot Android
BOARD_KERNEL_CMDLINE += loop.max_part=7
+BOARD_KERNEL_CMDLINE += init=/init
+
+BOARD_BOOTCONFIG += androidboot.hardware=cutf_cvm
# TODO(b/182417593): Move all of these module options to modules.options
# TODO(b/176860479): Remove once goldfish and cuttlefish share a wifi implementation
@@ -217,17 +256,17 @@
BOARD_BOOTCONFIG += \
kernel.vmw_vsock_virtio_transport_common.virtio_transport_max_vsock_pkt_buf_size=16384
-ifeq ($(TARGET_USERDATAIMAGE_FILE_SYSTEM_TYPE),f2fs)
-BOARD_BOOTCONFIG += androidboot.fstab_suffix=f2fs
-endif
-
-ifeq ($(TARGET_USERDATAIMAGE_FILE_SYSTEM_TYPE),ext4)
-BOARD_BOOTCONFIG += androidboot.fstab_suffix=ext4
-endif
+BOARD_BOOTCONFIG += \
+ androidboot.vendor.apex.com.android.wifi.hal=com.google.cf.wifi_hwsim \
+ androidboot.vendor.apex.com.google.emulated.camera.provider.hal=com.google.emulated.camera.provider.hal \
BOARD_INCLUDE_DTB_IN_BOOTIMG := true
+ifndef BOARD_BOOT_HEADER_VERSION
BOARD_BOOT_HEADER_VERSION := 4
+endif
BOARD_MKBOOTIMG_ARGS += --header_version $(BOARD_BOOT_HEADER_VERSION)
+BOARD_INIT_BOOT_HEADER_VERSION := 4
+BOARD_MKBOOTIMG_INIT_ARGS += --header_version $(BOARD_INIT_BOOT_HEADER_VERSION)
PRODUCT_COPY_FILES += \
device/google/cuttlefish/dtb.img:dtb.img \
device/google/cuttlefish/required_images:required_images \
@@ -246,16 +285,6 @@
endif
BOARD_MOVE_GSI_AVB_KEYS_TO_VENDOR_BOOT := true
-# TARGET_KERNEL_USE is defined in kernel.mk, if not defined in the environment variable.
-# Keep in sync with GKI APEX in device.mk
-ifneq (,$(TARGET_KERNEL_USE))
- ifneq (,$(filter 5.4, $(TARGET_KERNEL_USE)))
- BOARD_KERNEL_MODULE_INTERFACE_VERSIONS := 5.4-android12-0
- else
- BOARD_KERNEL_MODULE_INTERFACE_VERSIONS := $(TARGET_KERNEL_USE)-android12-unstable
- endif
-endif
-
BOARD_GENERIC_RAMDISK_KERNEL_MODULES_LOAD := dm-user.ko
BOARD_HAVE_BLUETOOTH := true
diff --git a/shared/auto/TEST_MAPPING b/shared/auto/TEST_MAPPING
new file mode 100644
index 0000000..99fe083
--- /dev/null
+++ b/shared/auto/TEST_MAPPING
@@ -0,0 +1,28 @@
+{
+ "auto-presubmit": [
+ {
+ "name": "AndroidCarApiTest"
+ },
+ {
+ "name": "CarSecurityPermissionTest"
+ },
+ {
+ "name": "CtsCarTestCases"
+ },
+ {
+ "name": "CtsCarBuiltinApiTestCases"
+ },
+ {
+ "name": "CtsCarHostTestCases"
+ },
+ {
+ "name": "CtsCarBuiltinApiHostTestCases"
+ },
+ {
+ "name": "CarServiceTest"
+ },
+ {
+ "name": "CarServiceUnitTest"
+ }
+ ]
+}
diff --git a/shared/auto/audio_policy_configuration.xml b/shared/auto/audio_policy_configuration.xml
index 93d4130..f1ac5ad 100644
--- a/shared/auto/audio_policy_configuration.xml
+++ b/shared/auto/audio_policy_configuration.xml
@@ -24,8 +24,8 @@
<audioPolicyConfiguration version="1.0" xmlns:xi="http://www.w3.org/2001/XInclude">
<!-- Global configuration Decalaration -->
<globalConfiguration speaker_drc_enabled="true"/>
-
- <module name="primary" halVersion="2.0">
+ <modules>
+ <module name="primary" halVersion="2.0">
<attachedDevices>
<item>Speaker</item>
<item>Built-In Mic</item>
@@ -61,7 +61,11 @@
<route type="mix" sink="primary input"
sources="Built-In Mic"/>
</routes>
- </module>
+ </module>
+
+ <!-- Remote Submix Audio HAL -->
+ <xi:include href="r_submix_audio_policy_configuration.xml"/>
+ </modules>
<xi:include href="audio_policy_volumes.xml"/>
<xi:include href="default_volume_tables.xml"/>
@@ -69,4 +73,4 @@
<!-- End of Volume section -->
<!-- End of Modules section -->
-</audioPolicyConfiguration>
\ No newline at end of file
+</audioPolicyConfiguration>
diff --git a/shared/auto/device.mk b/shared/auto/device_vendor.mk
similarity index 66%
rename from shared/auto/device.mk
rename to shared/auto/device_vendor.mk
index 3e6604e..277eaa3 100644
--- a/shared/auto/device.mk
+++ b/shared/auto/device_vendor.mk
@@ -14,13 +14,33 @@
# limitations under the License.
#
-################################################
-# Begin GCE specific configurations
-
DEVICE_MANIFEST_FILE += device/google/cuttlefish/shared/auto/manifest.xml
+PRODUCT_MANIFEST_FILES += device/google/cuttlefish/shared/config/product_manifest.xml
+SYSTEM_EXT_MANIFEST_FILES += device/google/cuttlefish/shared/config/system_ext_manifest.xml
+$(call inherit-product, $(SRC_TARGET_DIR)/product/handheld_vendor.mk)
+$(call inherit-product, frameworks/native/build/phone-xhdpi-2048-dalvik-heap.mk)
+$(call inherit-product, packages/services/Car/car_product/build/car.mk)
$(call inherit-product, device/google/cuttlefish/shared/device.mk)
+PRODUCT_VENDOR_PROPERTIES += \
+ keyguard.no_require_sim=true \
+ ro.cdma.home.operator.alpha=Android \
+ ro.cdma.home.operator.numeric=302780 \
+ ro.com.android.dataroaming=true \
+ ro.telephony.default_network=9 \
+
+# Cuttlefish RIL support
+TARGET_USES_CF_RILD ?= true
+ifeq ($(TARGET_USES_CF_RILD),true)
+$(call inherit-product, $(SRC_TARGET_DIR)/product/telephony_system_ext.mk)
+PRODUCT_PACKAGES += \
+ libcuttlefish-ril-2 \
+ libcuttlefish-rild
+else
+TARGET_NO_TELEPHONY := true
+endif
+
# Extend cuttlefish common sepolicy with auto-specific functionality
BOARD_SEPOLICY_DIRS += device/google/cuttlefish/shared/auto/sepolicy/vendor
@@ -28,8 +48,8 @@
# Begin general Android Auto Embedded configurations
PRODUCT_COPY_FILES += \
- packages/services/Car/car_product/init/init.bootstat.rc:$(TARGET_COPY_OUT_VENDOR)/etc/init/hw//init.bootstat.rc \
- packages/services/Car/car_product/init/init.car.rc:$(TARGET_COPY_OUT_VENDOR)/etc/init/hw//init.car.rc
+ packages/services/Car/car_product/init/init.bootstat.rc:$(TARGET_COPY_OUT_VENDOR)/etc/init/hw/init.bootstat.rc \
+ packages/services/Car/car_product/init/init.car.rc:$(TARGET_COPY_OUT_VENDOR)/etc/init/hw/init.car.rc
ifneq ($(LOCAL_SENSOR_FILE_OVERRIDES),true)
PRODUCT_COPY_FILES += \
@@ -38,32 +58,32 @@
endif
PRODUCT_COPY_FILES += \
+ frameworks/native/data/etc/car_core_hardware.xml:system/etc/permissions/car_core_hardware.xml \
frameworks/native/data/etc/android.hardware.bluetooth.xml:$(TARGET_COPY_OUT_VENDOR)/etc/permissions/android.hardware.bluetooth.xml \
frameworks/native/data/etc/android.hardware.broadcastradio.xml:$(TARGET_COPY_OUT_VENDOR)/etc/permissions/android.hardware.broadcastradio.xml \
frameworks/native/data/etc/android.hardware.faketouch.xml:$(TARGET_COPY_OUT_VENDOR)/etc/permissions/android.hardware.faketouch.xml \
frameworks/native/data/etc/android.hardware.screen.landscape.xml:$(TARGET_COPY_OUT_VENDOR)/etc/permissions/android.hardware.screen.landscape.xml \
frameworks/native/data/etc/android.software.activities_on_secondary_displays.xml:$(TARGET_COPY_OUT_VENDOR)/etc/permissions/android.software.activities_on_secondary_displays.xml \
- frameworks/native/data/etc/car_core_hardware.xml:system/etc/permissions/car_core_hardware.xml \
# Preinstalled packages per user type
PRODUCT_COPY_FILES += \
device/google/cuttlefish/shared/auto/preinstalled-packages-product-car-cuttlefish.xml:$(TARGET_COPY_OUT_PRODUCT)/etc/sysconfig/preinstalled-packages-product-car-cuttlefish.xml
-LOCAL_AUDIO_PRODUCT_COPY_FILES ?= \
+ifndef LOCAL_AUDIO_PRODUCT_COPY_FILES
+LOCAL_AUDIO_PRODUCT_COPY_FILES := \
device/google/cuttlefish/shared/auto/car_audio_configuration.xml:$(TARGET_COPY_OUT_VENDOR)/etc/car_audio_configuration.xml \
device/google/cuttlefish/shared/auto/audio_policy_configuration.xml:$(TARGET_COPY_OUT_VENDOR)/etc/audio_policy_configuration.xml \
frameworks/av/services/audiopolicy/config/a2dp_audio_policy_configuration.xml:$(TARGET_COPY_OUT_VENDOR)/etc/a2dp_audio_policy_configuration.xml \
- frameworks/av/services/audiopolicy/config/usb_audio_policy_configuration.xml:$(TARGET_COPY_OUT_VENDOR)/etc/usb_audio_policy_configuration.xml \
+ frameworks/av/services/audiopolicy/config/usb_audio_policy_configuration.xml:$(TARGET_COPY_OUT_VENDOR)/etc/usb_audio_policy_configuration.xml
+endif
-PRODUCT_VENDOR_PROPERTIES += \
- keyguard.no_require_sim=true \
- ro.cdma.home.operator.alpha=Android \
- ro.cdma.home.operator.numeric=302780 \
- ro.com.android.dataroaming=true \
+# Include display settings for an auto device.
+PRODUCT_COPY_FILES += \
+ device/google/cuttlefish/shared/auto/display_settings.xml:$(TARGET_COPY_OUT_VENDOR)/etc/display_settings.xml
# vehicle HAL
ifeq ($(LOCAL_VHAL_PRODUCT_PACKAGE),)
- LOCAL_VHAL_PRODUCT_PACKAGE := [email protected]
+ LOCAL_VHAL_PRODUCT_PACKAGE := android.hardware.automotive.vehicle@V1-emulator-service
BOARD_SEPOLICY_DIRS += device/google/cuttlefish/shared/auto/sepolicy/vhal
endif
PRODUCT_PACKAGES += $(LOCAL_VHAL_PRODUCT_PACKAGE)
@@ -74,6 +94,7 @@
# AudioControl HAL
ifeq ($(LOCAL_AUDIOCONTROL_HAL_PRODUCT_PACKAGE),)
LOCAL_AUDIOCONTROL_HAL_PRODUCT_PACKAGE := android.hardware.automotive.audiocontrol-service.example
+ BOARD_SEPOLICY_DIRS += device/google/cuttlefish/shared/auto/sepolicy/audio
endif
PRODUCT_PACKAGES += $(LOCAL_AUDIOCONTROL_HAL_PRODUCT_PACKAGE)
@@ -83,31 +104,39 @@
canhaldump \
canhalsend
-# Cuttlefish RIL support
-TARGET_USES_CF_RILD ?= false
-ifeq ($(TARGET_USES_CF_RILD),true)
- PRODUCT_PACKAGES += \
- libcuttlefish-ril-2 \
- libcuttlefish-rild
+# EVS
+# By default, we enable EvsManager, a sample EVS app, and a mock EVS HAL implementation.
+# If you want to use your own EVS HAL implementation, please set ENABLE_MOCK_EVSHAL as false
+# and add your HAL implementation to the product. Please also check init.evs.rc and see how
+# you can configure EvsManager to use your EVS HAL implementation. Similarly, please set
+# ENABLE_SAMPLE_EVS_APP as false if you want to use your own EVS app configuration or own EVS
+# app implementation.
+ENABLE_EVS_SERVICE ?= true
+ENABLE_MOCK_EVSHAL ?= true
+ENABLE_CAREVSSERVICE_SAMPLE ?= true
+ENABLE_SAMPLE_EVS_APP ?= true
+
+ifeq ($(ENABLE_MOCK_EVSHAL), true)
+CUSTOMIZE_EVS_SERVICE_PARAMETER := true
+PRODUCT_PACKAGES += [email protected] \
+ [email protected]
+PRODUCT_COPY_FILES += \
+ device/google/cuttlefish/shared/auto/evs/init.evs.rc:$(TARGET_COPY_OUT_VENDOR)/etc/init/init.evs.rc
+BOARD_SEPOLICY_DIRS += device/google/cuttlefish/shared/auto/sepolicy/evs
endif
-# system_other support
-PRODUCT_PACKAGES += \
- cppreopts.sh \
+ifeq ($(ENABLE_SAMPLE_EVS_APP), true)
+PRODUCT_PACKAGES += evs_app
+PRODUCT_COPY_FILES += \
+ device/google/cuttlefish/shared/auto/evs/evs_app_config.json:$(TARGET_COPY_OUT_SYSTEM)/etc/automotive/evs/config_override.json
+BOARD_SEPOLICY_DIRS += packages/services/Car/cpp/evs/apps/sepolicy/private
+endif
BOARD_IS_AUTOMOTIVE := true
-$(call inherit-product, $(SRC_TARGET_DIR)/product/aosp_base.mk)
-$(call inherit-product, frameworks/native/build/phone-xhdpi-2048-dalvik-heap.mk)
-$(call inherit-product, packages/services/Car/car_product/build/car.mk)
-
-# Placed here due to b/110784510
-PRODUCT_BRAND := generic
-
DEVICE_PACKAGE_OVERLAYS += device/google/cuttlefish/shared/auto/overlay
+PRODUCT_PACKAGES += CarServiceOverlayCuttleFish
+GOOGLE_CAR_SERVICE_OVERLAY += CarServiceOverlayCuttleFishGoogle
+
TARGET_BOARD_INFO_FILE ?= device/google/cuttlefish/shared/auto/android-info.txt
-
-PRODUCT_ENFORCE_RRO_TARGETS := framework-res
-
-TARGET_NO_TELEPHONY := true
diff --git a/shared/auto/display_settings.xml b/shared/auto/display_settings.xml
new file mode 100644
index 0000000..5065838
--- /dev/null
+++ b/shared/auto/display_settings.xml
@@ -0,0 +1,8 @@
+<?xml version='1.0' encoding='utf-8' standalone='yes' ?>
+<display-settings>
+ <!-- Use physical port number instead of local id -->
+ <config identifier="1" />
+
+ <!-- Display settings for cluster -->
+ <display name="port:1" dontMoveToTop="true" />
+</display-settings>
diff --git a/shared/auto/evs/evs_app_config.json b/shared/auto/evs/evs_app_config.json
new file mode 100644
index 0000000..a2be62d
--- /dev/null
+++ b/shared/auto/evs/evs_app_config.json
@@ -0,0 +1,43 @@
+
+{
+ "car" : {
+ "width" : 76.7,
+ "wheelBase" : 117.9,
+ "frontExtent" : 44.7,
+ "rearExtent" : 40
+ },
+ "displays" : [
+ {
+ "_comment": "Display0",
+ "displayPort" : 0,
+ "frontRange" : 100,
+ "rearRange" : 100
+ },
+ {
+ "_comment": "Display1",
+ "displayPort" : 1,
+ "frontRange" : 100,
+ "rearRange" : 100
+ }
+ ],
+ "graphic" : {
+ "frontPixel" : -20,
+ "rearPixel" : 260
+ },
+ "cameras" : [
+ {
+ "cameraId" : "/dev/video10",
+ "function" : "reverse",
+ "x" : 0.0,
+ "y" : 20.0,
+ "z" : 48,
+ "yaw" : 180,
+ "pitch" : -10,
+ "roll" : 0,
+ "hfov" : 115,
+ "vfov" : 80,
+ "hflip" : true,
+ "vflip" : false
+ }
+ ]
+}
diff --git a/shared/auto/evs/init.evs.rc b/shared/auto/evs/init.evs.rc
new file mode 100644
index 0000000..f7dace5
--- /dev/null
+++ b/shared/auto/evs/init.evs.rc
@@ -0,0 +1,11 @@
+on late-init
+ start automotive_display
+ start vendor.evs-hal-mock
+ start evs_manager_cf
+
+service evs_manager_cf /system/bin/evsmanagerd --target hw/0
+ class hal
+ priority -20
+ user automotive_evs
+ group automotive_evs system
+ disabled # will not automatically start with its class; must be explicitly started.
diff --git a/shared/auto/overlay/frameworks/base/core/res/res/values/config.xml b/shared/auto/overlay/frameworks/base/core/res/res/values/config.xml
index e42a10f..bf07d05 100644
--- a/shared/auto/overlay/frameworks/base/core/res/res/values/config.xml
+++ b/shared/auto/overlay/frameworks/base/core/res/res/values/config.xml
@@ -22,20 +22,16 @@
See also packages/services/Car/service/res/values/config.xml
-->
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <!-- Enable multi-user. -->
- <bool name="config_enableMultiUserUI" translatable="false">true</bool>
- <!-- If true, all guest users created on the device will be ephemeral. -->
- <bool name="config_guestUserEphemeral" translatable="false">true</bool>
- <!-- Maximum number of users allowed on the device. -->
- <integer name="config_multiuserMaximumUsers" translatable="false">4</integer>
- <!-- Restricting eth1 -->
- <string-array translatable="false" name="config_ethernet_interfaces">
- <item>eth1;11,12,14;;</item>
- </string-array>
- <!-- Car uses hardware amplifier for volume. -->
- <bool name="config_useFixedVolume">true</bool>
- <!--
- Handle volume keys directly in CarAudioService without passing them to the foreground app
- -->
- <bool name="config_handleVolumeKeysInWindowManager">true</bool>
+ <!-- Enable multi-user. -->
+ <bool name="config_enableMultiUserUI" translatable="false">true</bool>
+ <!-- If true, all guest users created on the device will be ephemeral. -->
+ <bool name="config_guestUserEphemeral" translatable="false">true</bool>
+ <!-- Maximum number of users allowed on the device. -->
+ <integer name="config_multiuserMaximumUsers" translatable="false">4</integer>
+ <!-- Car uses hardware amplifier for volume. -->
+ <bool name="config_useFixedVolume">true</bool>
+ <!--
+ Handle volume keys directly in CarAudioService without passing them to the foreground app
+ -->
+ <bool name="config_handleVolumeKeysInWindowManager">true</bool>
</resources>
diff --git a/shared/auto/preinstalled-packages-product-car-cuttlefish.xml b/shared/auto/preinstalled-packages-product-car-cuttlefish.xml
index a156ae8..9f22200 100644
--- a/shared/auto/preinstalled-packages-product-car-cuttlefish.xml
+++ b/shared/auto/preinstalled-packages-product-car-cuttlefish.xml
@@ -26,6 +26,10 @@
<install-in user-type="FULL" />
<install-in user-type="SYSTEM" />
</install-in-user-type>
+ <install-in-user-type package="com.android.localtransport">
+ <install-in user-type="FULL" />
+ <install-in user-type="SYSTEM" />
+ </install-in-user-type>
<install-in-user-type package="com.android.car.hvac">
<install-in user-type="FULL" />
<install-in user-type="SYSTEM" />
@@ -95,6 +99,22 @@
<install-in user-type="FULL" />
<install-in user-type="SYSTEM" />
</install-in-user-type>
+ <install-in-user-type package="com.android.apps.tag">
+ <install-in user-type="FULL" />
+ <install-in user-type="SYSTEM" />
+ </install-in-user-type>
+ <install-in-user-type package="com.android.imsserviceentitlement">
+ <install-in user-type="FULL" />
+ <install-in user-type="SYSTEM" />
+ </install-in-user-type>
+ <install-in-user-type package="com.android.cellbroadcastservice">
+ <install-in user-type="FULL" />
+ <install-in user-type="SYSTEM" />
+ </install-in-user-type>
+ <install-in-user-type package="com.android.cellbroadcastreceiver.module">
+ <install-in user-type="FULL" />
+ <install-in user-type="SYSTEM" />
+ </install-in-user-type>
<!--
@@ -119,9 +139,6 @@
<install-in-user-type package="com.android.dynsystem">
<install-in user-type="FULL" />
</install-in-user-type>
- <install-in-user-type package="com.android.localtransport">
- <install-in user-type="FULL" />
- </install-in-user-type>
<install-in-user-type package="com.android.mms.service">
<install-in user-type="FULL" />
</install-in-user-type>
diff --git a/shared/auto/rro_overlay/CarServiceOverlay/Android.bp b/shared/auto/rro_overlay/CarServiceOverlay/Android.bp
new file mode 100644
index 0000000..305e983
--- /dev/null
+++ b/shared/auto/rro_overlay/CarServiceOverlay/Android.bp
@@ -0,0 +1,35 @@
+// Copyright (C) 2022 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//
+
+package {
+ default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+runtime_resource_overlay {
+ name: "CarServiceOverlayCuttleFish",
+ resource_dirs: ["res"],
+ manifest: "AndroidManifest.xml",
+ sdk_version: "current",
+ product_specific: true
+}
+
+override_runtime_resource_overlay {
+ name: "CarServiceOverlayCuttleFishGoogle",
+ base: "CarServiceOverlayCuttleFish",
+ package_name: "com.google.android.car.resources.cuttlefish",
+ target_package_name: "com.google.android.car.updatable",
+}
+
diff --git a/shared/auto/rro_overlay/CarServiceOverlay/AndroidManifest.xml b/shared/auto/rro_overlay/CarServiceOverlay/AndroidManifest.xml
new file mode 100644
index 0000000..e3fc998
--- /dev/null
+++ b/shared/auto/rro_overlay/CarServiceOverlay/AndroidManifest.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2022 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.car.resources.cuttlefish">
+ <application android:hasCode="false"/>
+ <overlay android:priority="5001"
+ android:targetPackage="com.android.car.updatable"
+ android:targetName="CarServiceCustomization"
+ android:resourcesMap="@xml/overlays"
+ android:isStatic="true" />
+</manifest>
diff --git a/shared/auto/overlay/packages/services/Car/service/res/values/config.xml b/shared/auto/rro_overlay/CarServiceOverlay/res/values/config.xml
similarity index 100%
rename from shared/auto/overlay/packages/services/Car/service/res/values/config.xml
rename to shared/auto/rro_overlay/CarServiceOverlay/res/values/config.xml
diff --git a/shared/auto/rro_overlay/CarServiceOverlay/res/xml/overlays.xml b/shared/auto/rro_overlay/CarServiceOverlay/res/xml/overlays.xml
new file mode 100644
index 0000000..f3c730f
--- /dev/null
+++ b/shared/auto/rro_overlay/CarServiceOverlay/res/xml/overlays.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2022 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+<overlay>
+ <item target="bool/audioUseDynamicRouting" value="@bool/audioUseDynamicRouting" />
+ <item target="bool/audioUseCarVolumeGroupMuting" value="@bool/audioUseCarVolumeGroupMuting" />
+ <item target="bool/audioUseHalDuckingSignals" value="@bool/audioUseHalDuckingSignals" />
+ <item target="array/config_occupant_zones" value="@array/config_occupant_zones" />
+ <item target="array/config_occupant_display_mapping" value="@array/config_occupant_display_mapping" />
+ <item target="string/config_evsRearviewCameraId" value="@string/config_evsRearviewCameraId" />
+ <item target="string/config_evsCameraActivity" value="@string/config_evsCameraActivity" />
+</overlay>
diff --git a/shared/auto/sepolicy/audio/hal_audiocontrol_default.te b/shared/auto/sepolicy/audio/hal_audiocontrol_default.te
new file mode 100644
index 0000000..3ec4f5f
--- /dev/null
+++ b/shared/auto/sepolicy/audio/hal_audiocontrol_default.te
@@ -0,0 +1,2 @@
+# Enable audiocontrol to listen to power policy daemon.
+carpowerpolicy_callback_domain(hal_audiocontrol_default)
diff --git a/shared/auto/sepolicy/evs/automotive_display_service.te b/shared/auto/sepolicy/evs/automotive_display_service.te
new file mode 100644
index 0000000..d3d9ec0
--- /dev/null
+++ b/shared/auto/sepolicy/evs/automotive_display_service.te
@@ -0,0 +1,2 @@
+# Allow automotive_display_service to perform binder IPC to hal_evs_default
+binder_call(automotive_display_service_server, hal_evs_default)
diff --git a/shared/auto/sepolicy/evs/hal_evs_default.te b/shared/auto/sepolicy/evs/hal_evs_default.te
new file mode 100644
index 0000000..88a2934
--- /dev/null
+++ b/shared/auto/sepolicy/evs/hal_evs_default.te
@@ -0,0 +1,16 @@
+# Allow use of USB devices, gralloc buffers, and surface flinger
+hal_client_domain(hal_evs_default, hal_graphics_allocator);
+hal_client_domain(hal_evs_default, hal_graphics_composer)
+
+# Allow the driver to access EGL
+allow hal_evs_default gpu_device:chr_file rw_file_perms;
+allow hal_evs_default gpu_device:dir search;
+
+# Allow the driver to use SurfaceFlinger
+binder_call(hal_evs_default, surfaceflinger);
+allow hal_evs_default surfaceflinger_service:service_manager find;
+allow hal_evs_default ion_device:chr_file r_file_perms;
+
+# Allow the driver to use automotive display proxy service
+binder_call(hal_evs_default, automotive_display_service_server);
+allow hal_evs_default fwk_automotive_display_hwservice:hwservice_manager find;
diff --git a/shared/auto/sepolicy/evs/surfaceflinger.te b/shared/auto/sepolicy/evs/surfaceflinger.te
new file mode 100644
index 0000000..f23d190
--- /dev/null
+++ b/shared/auto/sepolicy/evs/surfaceflinger.te
@@ -0,0 +1,5 @@
+# Allow surfaceflinger to perform binder IPC to hal_evs_default
+binder_call(surfaceflinger, hal_evs_default)
+
+# Allow surfaceflinger to perform binder IPC to automotive_display_service
+binder_call(surfaceflinger, automotive_display_service_server)
diff --git a/shared/config/Android.bp b/shared/config/Android.bp
index 03bbc08..f2d94e4 100644
--- a/shared/config/Android.bp
+++ b/shared/config/Android.bp
@@ -2,6 +2,19 @@
default_applicable_licenses: ["Android-Apache-2.0"],
}
+filegroup {
+ name: "device_google_cuttlefish_shared_config_pci_ids",
+ srcs: ["pci.ids"],
+ licenses: ["device_google_cuttlefish_shared_config_pci_ids_license"],
+}
+
+license {
+ name: "device_google_cuttlefish_shared_config_pci_ids_license",
+ package_name: "PCI IDS",
+ license_kinds: ["SPDX-license-identifier-BSD-3-Clause"],
+ license_text: ["LICENSE_BSD"],
+}
+
prebuilt_etc_host {
name: "cvd_config_auto.json",
src: "config_auto.json",
@@ -15,12 +28,24 @@
}
prebuilt_etc_host {
+ name: "cvd_config_go.json",
+ src: "config_go.json",
+ sub_dir: "cvd_config",
+}
+
+prebuilt_etc_host {
name: "cvd_config_phone.json",
src: "config_phone.json",
sub_dir: "cvd_config",
}
prebuilt_etc_host {
+ name: "cvd_config_slim.json",
+ src: "config_slim.json",
+ sub_dir: "cvd_config",
+}
+
+prebuilt_etc_host {
name: "cvd_config_tablet.json",
src: "config_tablet.json",
sub_dir: "cvd_config",
@@ -31,3 +56,36 @@
src: "config_tv.json",
sub_dir: "cvd_config",
}
+
+prebuilt_etc_host {
+ name: "cvd_config_wear.json",
+ src: "config_wear.json",
+ sub_dir: "cvd_config",
+}
+
+prebuilt_etc_host {
+ name: "grub.cfg",
+ src: "grub.cfg",
+ sub_dir: "grub",
+}
+
+prebuilt_etc {
+ name: "wpa_supplicant_overlay.conf.cf",
+ src: "wpa_supplicant_overlay.conf",
+ filename_from_src: true,
+ relative_install_path: "wifi",
+ installable: false,
+}
+
+prebuilt_etc {
+ name: "p2p_supplicant.conf.cf",
+ src: "p2p_supplicant.conf",
+ filename_from_src: true,
+ relative_install_path: "wifi",
+ installable: false,
+}
+
+filegroup {
+ name: "[email protected]",
+ srcs: ["[email protected]"]
+}
diff --git a/shared/config/CleanSpec.mk b/shared/config/CleanSpec.mk
index 9556fc5..a388176 100644
--- a/shared/config/CleanSpec.mk
+++ b/shared/config/CleanSpec.mk
@@ -51,3 +51,4 @@
$(call add-clean-step, rm -rf $(PRODUCT_OUT)/vendor/etc/init/[email protected])
$(call add-clean-step, rm -rf $(PRODUCT_OUT)/vendor/bin/hw/[email protected])
$(call add-clean-step, rm -rf $(PRODUCT_OUT)/vendor/bin/hw/[email protected])
+$(call add-clean-step, find $(PRODUCT_OUT)/vendor/bin/hw/ -type f -name "android.hardware.drm*" -print0 | xargs -0 rm -f)
diff --git a/shared/config/LICENSE_BSD b/shared/config/LICENSE_BSD
new file mode 100644
index 0000000..540b631
--- /dev/null
+++ b/shared/config/LICENSE_BSD
@@ -0,0 +1,24 @@
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+1. Redistributions of source code must retain the above copyright notice, this
+ list of conditions and the following disclaimer.
+
+2. Redistributions in binary form must reproduce the above copyright notice,
+ this list of conditions and the following disclaimer in the documentation
+ and/or other materials provided with the distribution.
+
+3. Neither the name of the copyright holder nor the names of its contributors
+ may be used to endorse or promote products derived from this software
+ without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/shared/config/audio_policy.conf b/shared/config/audio_policy.conf
deleted file mode 100644
index 2cbf118..0000000
--- a/shared/config/audio_policy.conf
+++ /dev/null
@@ -1,64 +0,0 @@
-#
-# Audio policy configuration for generic device builds (cuttlefish audio HAL)
-#
-
-# Global configuration section: lists input and output devices always present on the device
-# as well as the output device selected by default.
-# Devices are designated by a string that corresponds to the enum in audio.h
-
-global_configuration {
- attached_output_devices AUDIO_DEVICE_OUT_SPEAKER
- default_output_device AUDIO_DEVICE_OUT_SPEAKER
- attached_input_devices AUDIO_DEVICE_IN_BUILTIN_MIC|AUDIO_DEVICE_IN_REMOTE_SUBMIX
-}
-
-# audio hardware module section: contains descriptors for all audio hw modules present on the
-# device. Each hw module node is named after the corresponding hw module library base name.
-# For instance, "primary" corresponds to audio.primary.<device>.so.
-# The "primary" module is mandatory and must include at least one output with
-# AUDIO_OUTPUT_FLAG_PRIMARY flag.
-# Each module descriptor contains one or more output profile descriptors and zero or more
-# input profile descriptors. Each profile lists all the parameters supported by a given output
-# or input stream category.
-# The "channel_masks", "formats", "devices" and "flags" are specified using strings corresponding
-# to enums in audio.h and audio_policy.h. They are concatenated by use of "|" without space or "\n".
-
-audio_hw_modules {
- primary {
- outputs {
- primary {
- sampling_rates 8000|11025|16000|22050|24000|44100|48000
- channel_masks AUDIO_CHANNEL_OUT_MONO|AUDIO_CHANNEL_OUT_STEREO
- formats AUDIO_FORMAT_PCM_16_BIT
- devices AUDIO_DEVICE_OUT_SPEAKER|AUDIO_DEVICE_OUT_WIRED_HEADPHONE|AUDIO_DEVICE_OUT_WIRED_HEADSET
- flags AUDIO_OUTPUT_FLAG_PRIMARY
- }
- }
- inputs {
- primary {
- sampling_rates 8000|11025|16000|22050|44100|48000
- channel_masks AUDIO_CHANNEL_IN_MONO|AUDIO_CHANNEL_IN_STEREO
- formats AUDIO_FORMAT_PCM_16_BIT
- devices AUDIO_DEVICE_IN_BUILTIN_MIC|AUDIO_DEVICE_IN_WIRED_HEADSET
- }
- }
- }
- r_submix {
- outputs {
- submix {
- sampling_rates 48000
- channel_masks AUDIO_CHANNEL_OUT_STEREO
- formats AUDIO_FORMAT_PCM_16_BIT
- devices AUDIO_DEVICE_OUT_REMOTE_SUBMIX
- }
- }
- inputs {
- submix {
- sampling_rates 48000
- channel_masks AUDIO_CHANNEL_IN_STEREO
- formats AUDIO_FORMAT_PCM_16_BIT
- devices AUDIO_DEVICE_IN_REMOTE_SUBMIX
- }
- }
- }
-}
diff --git a/shared/config/config_auto.json b/shared/config/config_auto.json
index b8f2bfe..67dd3bc 100644
--- a/shared/config/config_auto.json
+++ b/shared/config/config_auto.json
@@ -1,6 +1,5 @@
{
- "x_res" : 1280,
- "y_res" : 800,
- "dpi" : 160,
+ "display0": "width=1080,height=600,dpi=120",
+ "display1": "width=400,height=600,dpi=120",
"memory_mb" : 4096
}
diff --git a/shared/config/config_go.json b/shared/config/config_go.json
new file mode 100644
index 0000000..69ad977
--- /dev/null
+++ b/shared/config/config_go.json
@@ -0,0 +1,6 @@
+{
+ "x_res" : 720,
+ "y_res" : 1280,
+ "dpi" : 320,
+ "memory_mb" : 2048
+}
diff --git a/shared/config/config_slim.json b/shared/config/config_slim.json
new file mode 100644
index 0000000..017057f
--- /dev/null
+++ b/shared/config/config_slim.json
@@ -0,0 +1,7 @@
+{
+ "x_res" : 720,
+ "y_res" : 1280,
+ "dpi" : 320,
+ "memory_mb" : 2048,
+ "use_sdcard" : false
+}
diff --git a/shared/config/config_wear.json b/shared/config/config_wear.json
new file mode 100644
index 0000000..59e76ad
--- /dev/null
+++ b/shared/config/config_wear.json
@@ -0,0 +1,7 @@
+{
+ "x_res" : 450,
+ "y_res" : 450,
+ "dpi" : 320,
+ "memory_mb" : 1536,
+ "use_sdcard" : false
+}
diff --git a/shared/config/fstab-erofs.ext4 b/shared/config/fstab-erofs.ext4
deleted file mode 100644
index ec7b3fe..0000000
--- a/shared/config/fstab-erofs.ext4
+++ /dev/null
@@ -1,18 +0,0 @@
-/dev/block/by-name/boot /boot emmc defaults recoveryonly,slotselect
-/dev/block/by-name/vendor_boot /vendor_boot emmc defaults recoveryonly,slotselect
-system /system erofs ro wait,logical,first_stage_mount,slotselect,avb=vbmeta_system
-# Add all non-dynamic partitions except system, after this comment
-/dev/block/by-name/userdata /data ext4 nodev,noatime,nosuid,errors=panic latemount,wait,check,quota,formattable,fileencryption=aes-256-xts:aes-256-cts,keydirectory=/metadata/vold/metadata_encryption,checkpoint=block
-/dev/block/by-name/metadata /metadata ext4 nodev,noatime,nosuid,errors=panic wait,formattable,first_stage_mount,check
-/dev/block/by-name/misc /misc emmc defaults defaults
-# Add all dynamic partitions except system, after this comment
-odm /odm erofs ro wait,logical,first_stage_mount,slotselect,avb
-product /product erofs ro wait,logical,first_stage_mount,slotselect,avb
-system_ext /system_ext erofs ro wait,logical,first_stage_mount,slotselect,avb=vbmeta_system
-vendor /vendor erofs ro wait,logical,first_stage_mount,slotselect,avb=vbmeta
-vendor_dlkm /vendor_dlkm ext4 noatime,ro,errors=panic wait,logical,first_stage_mount,slotselect,avb
-odm_dlkm /odm_dlkm ext4 noatime,ro,errors=panic wait,logical,first_stage_mount,slotselect,avb
-/dev/block/zram0 none swap defaults zramsize=75%
-/dev/block/vdc1 /sdcard vfat defaults recoveryonly
-/devices/*/block/vdc auto auto defaults voldmanaged=sdcard1:auto,encryptable=userdata
-shared /mnt/vendor/shared virtiofs nosuid,nodev,noatime nofail
diff --git a/shared/config/fstab-erofs.f2fs b/shared/config/fstab-erofs.f2fs
deleted file mode 100644
index 1b5486e..0000000
--- a/shared/config/fstab-erofs.f2fs
+++ /dev/null
@@ -1,18 +0,0 @@
-/dev/block/by-name/boot /boot emmc defaults recoveryonly,slotselect
-/dev/block/by-name/vendor_boot /vendor_boot emmc defaults recoveryonly,slotselect
-system /system erofs ro wait,logical,first_stage_mount,slotselect,avb=vbmeta_system
-# Add all non-dynamic partitions except system, after this comment
-/dev/block/by-name/userdata /data f2fs nodev,noatime,nosuid,inlinecrypt,reserve_root=32768 latemount,wait,check,quota,formattable,fileencryption=aes-256-xts:aes-256-cts:v2+inlinecrypt_optimized,fscompress,keydirectory=/metadata/vold/metadata_encryption,checkpoint=fs
-/dev/block/by-name/metadata /metadata ext4 nodev,noatime,nosuid,errors=panic wait,formattable,first_stage_mount,check
-/dev/block/by-name/misc /misc emmc defaults defaults
-# Add all dynamic partitions except system, after this comment
-odm /odm erofs ro wait,logical,first_stage_mount,slotselect,avb
-product /product erofs ro wait,logical,first_stage_mount,slotselect,avb
-system_ext /system_ext erofs ro wait,logical,first_stage_mount,slotselect,avb=vbmeta_system
-vendor /vendor erofs ro wait,logical,first_stage_mount,slotselect,avb=vbmeta
-vendor_dlkm /vendor_dlkm ext4 noatime,ro,errors=panic wait,logical,first_stage_mount,slotselect,avb
-odm_dlkm /odm_dlkm ext4 noatime,ro,errors=panic wait,logical,first_stage_mount,slotselect,avb
-/dev/block/zram0 none swap defaults zramsize=75%
-/dev/block/vdc1 /sdcard vfat defaults recoveryonly
-/devices/*/block/vdc auto auto defaults voldmanaged=sdcard1:auto,encryptable=userdata
-shared /mnt/vendor/shared virtiofs nosuid,nodev,noatime nofail
diff --git a/shared/config/fstab.ext4 b/shared/config/fstab.ext4
index 1677941..4d3fe9b 100644
--- a/shared/config/fstab.ext4
+++ b/shared/config/fstab.ext4
@@ -1,17 +1,29 @@
-/dev/block/by-name/boot /boot emmc defaults recoveryonly,slotselect
+# Non-dynamic, boot critical partitions
+/dev/block/by-name/boot /boot emmc defaults recoveryonly,slotselect,first_stage_mount,avb=boot
+/dev/block/by-name/init_boot /init_boot emmc defaults recoveryonly,slotselect,first_stage_mount,avb=init_boot
/dev/block/by-name/vendor_boot /vendor_boot emmc defaults recoveryonly,slotselect
-system /system ext4 noatime,ro,errors=panic wait,logical,first_stage_mount,slotselect,avb=vbmeta_system
+system /system erofs ro wait,logical,first_stage_mount,slotselect,avb=vbmeta_system,avb_keys=/avb
+system /system ext4 noatime,ro,errors=panic wait,logical,first_stage_mount,slotselect,avb=vbmeta_system,avb_keys=/avb
# Add all non-dynamic partitions except system, after this comment
/dev/block/by-name/userdata /data ext4 nodev,noatime,nosuid,errors=panic latemount,wait,check,quota,formattable,fileencryption=aes-256-xts:aes-256-cts,keydirectory=/metadata/vold/metadata_encryption,checkpoint=block
/dev/block/by-name/metadata /metadata ext4 nodev,noatime,nosuid,errors=panic wait,formattable,first_stage_mount,check
/dev/block/by-name/misc /misc emmc defaults defaults
# Add all dynamic partitions except system, after this comment
+odm /odm erofs ro wait,logical,first_stage_mount,slotselect,avb
odm /odm ext4 noatime,ro,errors=panic wait,logical,first_stage_mount,slotselect,avb
+product /product erofs ro wait,logical,first_stage_mount,slotselect,avb
product /product ext4 noatime,ro,errors=panic wait,logical,first_stage_mount,slotselect,avb
+system_ext /system_ext erofs ro wait,logical,first_stage_mount,slotselect,avb=vbmeta_system
system_ext /system_ext ext4 noatime,ro,errors=panic wait,logical,first_stage_mount,slotselect,avb=vbmeta_system
+vendor /vendor erofs ro wait,logical,first_stage_mount,slotselect,avb=vbmeta
vendor /vendor ext4 noatime,ro,errors=panic wait,logical,first_stage_mount,slotselect,avb=vbmeta
+vendor_dlkm /vendor_dlkm erofs ro wait,logical,first_stage_mount,slotselect,avb
vendor_dlkm /vendor_dlkm ext4 noatime,ro,errors=panic wait,logical,first_stage_mount,slotselect,avb
+odm_dlkm /odm_dlkm erofs ro wait,logical,first_stage_mount,slotselect,avb
odm_dlkm /odm_dlkm ext4 noatime,ro,errors=panic wait,logical,first_stage_mount,slotselect,avb
+system_dlkm /system_dlkm erofs ro wait,logical,first_stage_mount,slotselect,avb=vbmeta
+system_dlkm /system_dlkm ext4 noatime,ro,errors=panic wait,logical,first_stage_mount,slotselect,avb=vbmeta
+# ZRAM, SD-Card and virtiofs shares
/dev/block/zram0 none swap defaults zramsize=75%
/dev/block/vdc1 /sdcard vfat defaults recoveryonly
/devices/*/block/vdc auto auto defaults voldmanaged=sdcard1:auto,encryptable=userdata
diff --git a/shared/config/fstab.f2fs b/shared/config/fstab.f2fs
index 597e3f0..41162ed 100644
--- a/shared/config/fstab.f2fs
+++ b/shared/config/fstab.f2fs
@@ -1,17 +1,29 @@
-/dev/block/by-name/boot /boot emmc defaults recoveryonly,slotselect
+# Non-dynamic, boot critical partitions
+/dev/block/by-name/boot /boot emmc defaults recoveryonly,slotselect,first_stage_mount,avb=boot
+/dev/block/by-name/init_boot /init_boot emmc defaults recoveryonly,slotselect,first_stage_mount,avb=init_boot
/dev/block/by-name/vendor_boot /vendor_boot emmc defaults recoveryonly,slotselect
-system /system ext4 noatime,ro,errors=panic wait,logical,first_stage_mount,slotselect,avb=vbmeta_system
+system /system erofs ro wait,logical,first_stage_mount,slotselect,avb=vbmeta_system,avb_keys=/avb
+system /system ext4 noatime,ro,errors=panic wait,logical,first_stage_mount,slotselect,avb=vbmeta_system,avb_keys=/avb
# Add all non-dynamic partitions except system, after this comment
/dev/block/by-name/userdata /data f2fs nodev,noatime,nosuid,inlinecrypt,reserve_root=32768 latemount,wait,check,quota,formattable,fileencryption=aes-256-xts:aes-256-cts:v2+inlinecrypt_optimized,fscompress,keydirectory=/metadata/vold/metadata_encryption,checkpoint=fs
/dev/block/by-name/metadata /metadata ext4 nodev,noatime,nosuid,errors=panic wait,formattable,first_stage_mount,check
/dev/block/by-name/misc /misc emmc defaults defaults
# Add all dynamic partitions except system, after this comment
+odm /odm erofs ro wait,logical,first_stage_mount,slotselect,avb
odm /odm ext4 noatime,ro,errors=panic wait,logical,first_stage_mount,slotselect,avb
+product /product erofs ro wait,logical,first_stage_mount,slotselect,avb
product /product ext4 noatime,ro,errors=panic wait,logical,first_stage_mount,slotselect,avb
+system_ext /system_ext erofs ro wait,logical,first_stage_mount,slotselect,avb=vbmeta_system
system_ext /system_ext ext4 noatime,ro,errors=panic wait,logical,first_stage_mount,slotselect,avb=vbmeta_system
+vendor /vendor erofs ro wait,logical,first_stage_mount,slotselect,avb=vbmeta
vendor /vendor ext4 noatime,ro,errors=panic wait,logical,first_stage_mount,slotselect,avb=vbmeta
+vendor_dlkm /vendor_dlkm erofs ro wait,logical,first_stage_mount,slotselect,avb
vendor_dlkm /vendor_dlkm ext4 noatime,ro,errors=panic wait,logical,first_stage_mount,slotselect,avb
+odm_dlkm /odm_dlkm erofs ro wait,logical,first_stage_mount,slotselect,avb
odm_dlkm /odm_dlkm ext4 noatime,ro,errors=panic wait,logical,first_stage_mount,slotselect,avb
+system_dlkm /system_dlkm erofs ro wait,logical,first_stage_mount,slotselect,avb=vbmeta
+system_dlkm /system_dlkm ext4 noatime,ro,errors=panic wait,logical,first_stage_mount,slotselect,avb=vbmeta
+# ZRAM, SD-Card and virtiofs shares
/dev/block/zram0 none swap defaults zramsize=75%
/dev/block/vdc1 /sdcard vfat defaults recoveryonly
/devices/*/block/vdc auto auto defaults voldmanaged=sdcard1:auto,encryptable=userdata
diff --git a/shared/config/grub.cfg b/shared/config/grub.cfg
new file mode 100644
index 0000000..d15eb8e
--- /dev/null
+++ b/shared/config/grub.cfg
@@ -0,0 +1,43 @@
+# Root grub.cfg used either to boot raw kernel and/or initramfs.img, or to
+# chain to an installed distro's GRUB configuration file
+
+# These options are accessible to chain-loaded configurations as well:
+#
+# pnpacpi=off Disable on QEMU; allows serdev to claim platform serial
+# acpi=noirq Do not configure IRQ routing using ACPI tables
+# reboot=k Reboot using keyboard method, rather than ACPI
+# noexec=off Some kernels panic when setting up NX
+# noefi Some kernels panic when trying to use U-Boot EFI
+# panic=-1 Don't reboot on panic
+# console=hvc0 Switch kernel logging to virtio-console once available
+# console=ttyAMA0 QEMU on ARM64 uses alternative serial implementation
+#
+if [ "$grub_cpu" = "i386" ]; then
+ set cmdline="pnpacpi=off acpi=noirq reboot=k noexec=off console=ttyS0 noefi panic=-1 console=hvc0"
+elif [ "$grub_cpu" = "arm64" ]; then
+ set cmdline="console=ttyS0 console=ttyAMA0 noefi panic=-1 console=hvc0"
+else
+ echo "Warning: No architecture found for ${grub_cpu}"
+fi
+
+# Root filesystem is on a GUID partition with label "otheros_root"
+set rootfs="/dev/vda14"
+
+# Root filesystem with grub installed
+search --file --set root /boot/grub/grub.cfg --hint (hd0)
+if [ $? = 0 ]; then
+ set prefix=($root)/boot/grub
+ export cmdline
+ export rootfs
+ configfile $prefix/grub.cfg
+ normal_exit
+fi
+
+# Fall back if we couldn't chain to another GRUB install
+set timeout=0
+menuentry "Linux" {
+ linux /vmlinuz $cmdline root=$rootfs
+ if [ -e /initrd.img ]; then
+ initrd /initrd.img
+ fi
+}
diff --git a/shared/config/init.insmod.sh b/shared/config/init.insmod.sh
deleted file mode 100755
index ccbf716..0000000
--- a/shared/config/init.insmod.sh
+++ /dev/null
@@ -1,49 +0,0 @@
-#!/vendor/bin/sh
-
-# Copyright (C) 2019 The Android Open Source Project
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-#
-
-KERNEL_VERSION_NUMBER=`uname -r`
-MAINLINE_STR='mainline'
-if [[ $KERNEL_VERSION_NUMBER == *$MAINLINE_STR* ]]; then
- IS_MAINLINE=1
-else
- IS_MAINLINE=0
-fi
-
-KERNEL_VERSION_NUMBER=`echo $KERNEL_VERSION_NUMBER | grep -o -E '^[0-9]+\.[0-9]+'`
-# This folder on cuttlefish contains modules for multiple kernel versions.
-# Hence the need to filter them instead of relying on module.order
-VENDOR_MODULES='/vendor/lib/modules/*.ko'
-
-for f in $VENDOR_MODULES
-do
- MOD_VERSION=`modinfo $f`
- MOD_VERSION=`echo $MOD_VERSION | grep -o -E 'vermagic: [0-9a-zA-Z\.-]+'`
- MOD_VERSION_NUMBER=`echo $MOD_VERSION | grep -o -E '[0-9]+\.[0-9]+'`
- if [[ $MOD_VERSION == *$MAINLINE_STR* ]]; then
- IS_MOD_MAINLINE=1
- else
- IS_MOD_MAINLINE=0
- fi
-
- # TODO (137683279) When we have a few more kernel modules, we'll have to do the module
- # insertion of least dependencies.
- if [ $IS_MOD_MAINLINE -eq $IS_MAINLINE ] && [ $MOD_VERSION_NUMBER == $KERNEL_VERSION_NUMBER ]
- then
- `insmod $f`
- echo "Insmod " $f
- fi
-done
diff --git a/shared/config/init.vendor.rc b/shared/config/init.vendor.rc
index 94ef593..8861ca3 100644
--- a/shared/config/init.vendor.rc
+++ b/shared/config/init.vendor.rc
@@ -1,19 +1,19 @@
on early-init
# loglevel 8
- mount securityfs securityfs /sys/kernel/security
-
setprop ro.sf.lcd_density ${ro.boot.lcd_density}
setprop ro.hardware.egl ${ro.boot.hardware.egl}
setprop debug.sf.vsync_reactor_ignore_present_fences true
setprop ro.hardware.gralloc ${ro.boot.hardware.gralloc}
setprop ro.hardware.hwcomposer ${ro.boot.hardware.hwcomposer}
+ setprop ro.vendor.hwcomposer.mode ${ro.boot.hardware.hwcomposer.mode}
setprop ro.hardware.vulkan ${ro.boot.hardware.vulkan}
setprop ro.cpuvulkan.version ${ro.boot.cpuvulkan.version}
setprop ro.hw_timeout_multiplier ${ro.boot.hw_timeout_multiplier}
+ setprop ro.opengles.version ${ro.boot.opengles.version}
# start module load in the background
- start vendor.insmod_sh
+ start vendor.dlkm_loader
on init
# ZRAM setup
@@ -47,10 +47,6 @@
mount_all --early
restorecon_recursive /vendor
- start setup_wifi
- # works around framework netiface enumeration issue
- start rename_eth0
-
# So GceBootReporter can print to kmsg
chmod 622 /dev/kmsg
@@ -58,38 +54,40 @@
# set RLIMIT_MEMLOCK to 64MB
setrlimit 8 67108864 67108864
- start bt_vhci_forwarder
+on post-fs-data
+ # works around framework netiface enumeration issue
+ # TODO(b/202731768): Add this `start rename_eth0` command to the init.rc for rename_netiface
+ start rename_eth0
+
+on post-fs-data && property:ro.vendor.wifi_impl=virt_wifi
+ # TODO(b/202731768): Add this `start setup_wifi` command to the init.rc for setup_wifi
+ start setup_wifi
on post-fs-data
mkdir /data/vendor/modem_dump 0777 system system
mkdir /data/vendor/radio 0777 system system
on late-fs
- # Wait for keymaster
- exec_start wait_for_keymaster
-
# Mount RW partitions which need run fsck
mount_all --late
write /dev/kmsg "GUEST_BUILD_FINGERPRINT: ${ro.build.fingerprint}"
+on post-fs-data && property:ro.vendor.wifi_impl=mac8011_hwsim_virtio
+ mkdir /data/vendor/wifi 0770 wifi wifi
+ mkdir /data/vendor/wifi/hostapd 0770 wifi wifi
+ mkdir /data/vendor/wifi/hostapd/sockets 0770 wifi wifi
+ start init_wifi_sh
+
on boot
- chmod 0660 /dev/cpuctl
+ chmod 0770 /dev/cpuctl
mkdir /data/vendor/wifi 0770 wifi wifi
mkdir /data/vendor/wifi/wpa 0770 wifi wifi
mkdir /data/vendor/wifi/wpa/sockets 0770 wifi wifi
start socket_vsock_proxy
setprop ro.hardware.audio.primary goldfish
-
-service bt_vhci_forwarder /vendor/bin/bt_vhci_forwarder -virtio_console_dev=${vendor.ser.bt-uart}
- user bluetooth
- group bluetooth
-
-service setup_wifi /vendor/bin/setup_wifi
- oneshot
-
-service rename_eth0 /vendor/bin/rename_netiface eth0 rmnet0
- oneshot
+ symlink /dev/hvc6 /dev/gnss0
+ symlink /dev/hvc7 /dev/gnss1
on property:sys.boot_completed=1
trigger sys-boot-completed-set
@@ -101,7 +99,7 @@
on sys-boot-completed-set && property:persist.sys.zram_enabled=1
swapon_all
-service vendor.insmod_sh /vendor/bin/init.insmod.sh
+service vendor.dlkm_loader /vendor/bin/dlkm_loader
class main
user root
group root system
@@ -127,17 +125,6 @@
enable vsoc_input_service
start vsoc_input_service
-service wpa_supplicant /vendor/bin/hw/wpa_supplicant -g@android:wpa_wlan0
- interface [email protected]::ISupplicant default
- interface [email protected]::ISupplicant default
- interface [email protected]::ISupplicant default
- interface [email protected]::ISupplicant default
- interface [email protected]::ISupplicant default
- socket wpa_wlan0 dgram 660 wifi wifi
- group system wifi inet
- disabled
- oneshot
-
service bugreport /system/bin/dumpstate -d -p -z
class main
disabled
diff --git a/shared/config/input/Android.bp b/shared/config/input/Android.bp
new file mode 100644
index 0000000..3c85557
--- /dev/null
+++ b/shared/config/input/Android.bp
@@ -0,0 +1,47 @@
+// Copyright (C) 2021 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package {
+ default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+prebuilt_defaults {
+ name: "crosvm_idc_defaults",
+ relative_install_path: "usr/idc",
+ soc_specific: true,
+}
+
+prebuilt_etc {
+ name: "Crosvm_Virtio_Multitouch_Touchscreen_0.idc",
+ src: "Crosvm_Virtio_Multitouch_Touchscreen_0.idc",
+ defaults: ["crosvm_idc_defaults"],
+}
+
+prebuilt_etc {
+ name: "Crosvm_Virtio_Multitouch_Touchscreen_1.idc",
+ src: "Crosvm_Virtio_Multitouch_Touchscreen_1.idc",
+ defaults: ["crosvm_idc_defaults"],
+}
+
+prebuilt_etc {
+ name: "Crosvm_Virtio_Multitouch_Touchscreen_2.idc",
+ src: "Crosvm_Virtio_Multitouch_Touchscreen_2.idc",
+ defaults: ["crosvm_idc_defaults"],
+}
+
+prebuilt_etc {
+ name: "Crosvm_Virtio_Multitouch_Touchscreen_3.idc",
+ src: "Crosvm_Virtio_Multitouch_Touchscreen_3.idc",
+ defaults: ["crosvm_idc_defaults"],
+}
diff --git a/shared/config/manifest.xml b/shared/config/manifest.xml
index 5917647..126557f 100644
--- a/shared/config/manifest.xml
+++ b/shared/config/manifest.xml
@@ -16,7 +16,7 @@
** limitations under the License.
*/
-->
-<manifest version="1.0" type="device" target-level="6">
+<manifest version="1.0" type="device" target-level="7">
<hal format="hidl">
<name>android.hardware.audio.effect</name>
<transport>hwbinder</transport>
@@ -57,15 +57,6 @@
</interface>
</hal>
-->
- <hal format="hidl">
- <name>android.hardware.bluetooth.audio</name>
- <transport>hwbinder</transport>
- <version>2.1</version>
- <interface>
- <name>IBluetoothAudioProvidersFactory</name>
- <instance>default</instance>
- </interface>
- </hal>
<!-- TODO (b/130078386):
<hal format="hidl">
<name>android.hardware.confirmationui</name>
diff --git a/shared/tv/manifest.xml b/shared/config/[email protected]
similarity index 60%
rename from shared/tv/manifest.xml
rename to shared/config/[email protected]
index 8aab6ee..4d8cc57 100644
--- a/shared/tv/manifest.xml
+++ b/shared/config/[email protected]
@@ -1,7 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
/*
-** Copyright 2017, The Android Open Source Project.
+** Copyright 2022, The Android Open Source Project.
**
** Licensed under the Apache License, Version 2.0 (the "License");
** you may not use this file except in compliance with the License.
@@ -17,26 +17,13 @@
*/
-->
<manifest version="1.0" type="device">
- <!-- FIXME: Implement tv.cec HAL
- <hal format="hidl">
- <name>android.hardware.tv.cec</name>
- <transport>hwbinder</transport>
- <version>1.0</version>
- <interface>
- <name>IHdmiCec</name>
- <instance>default</instance>
- </interface>
- </hal>
- -->
- <!-- FIXME: Implement tv.input HAL
<hal format="hidl">
- <name>android.hardware.tv.input</name>
+ <name>android.hardware.graphics.composer</name>
<transport>hwbinder</transport>
- <version>1.0</version>
+ <version>2.4</version>
<interface>
- <name>ITvInput</name>
+ <name>IComposer</name>
<instance>default</instance>
</interface>
</hal>
- -->
-</manifest>
+</manifest>
\ No newline at end of file
diff --git a/shared/config/p2p_supplicant.conf b/shared/config/p2p_supplicant.conf
new file mode 100644
index 0000000..2512889
--- /dev/null
+++ b/shared/config/p2p_supplicant.conf
@@ -0,0 +1,7 @@
+disable_scan_offload=1
+update_config=1
+p2p_go_vht=1
+p2p_listen_reg_class=81
+p2p_listen_channel=1
+p2p_oper_reg_class=81
+p2p_oper_channel=1
diff --git a/shared/config/pci.ids b/shared/config/pci.ids
new file mode 100644
index 0000000..790e62b
--- /dev/null
+++ b/shared/config/pci.ids
@@ -0,0 +1,69 @@
+#
+# List of PCI ID's
+#
+# Version: 2022.02.09
+# Date: 2022-02-09 03:15:01
+#
+# Maintained by Albert Pool, Martin Mares, and other volunteers from
+# the PCI ID Project at https://pci-ids.ucw.cz/.
+#
+# New data are always welcome, especially if they are accurate. If you have
+# anything to contribute, please follow the instructions at the web site.
+#
+# This file can be distributed under either the GNU General Public License
+# (version 2 or higher) or the 3-clause BSD License.
+#
+# The database is a compilation of factual data, and as such the copyright
+# only covers the aggregation and formatting. The copyright is held by
+# Martin Mares and Albert Pool.
+#
+
+# Vendors, devices and subsystems. Please keep sorted.
+
+# Syntax:
+# vendor vendor_name
+# device device_name <-- single tab
+# subvendor subdevice subsystem_name <-- two tabs
+
+# ANDROID:
+# This is a heavily reduced subset of devices put together to work around an
+# issue in toybox lspci. Toybox lspci doesn't fully understand the pci.ids
+# format, and gets stuck in "QEMU Virtual Machine" devices in other sections.
+
+1af4 Red Hat, Inc.
+ 1000 Virtio network device
+ 1001 Virtio block device
+ 1002 Virtio memory balloon
+ 1003 Virtio console
+ 1004 Virtio SCSI
+ 1005 Virtio RNG
+ 1009 Virtio filesystem
+# virtio 1.0
+ 1041 Virtio network device
+# virtio 1.0
+ 1042 Virtio block device
+# virtio 1.0
+ 1043 Virtio console
+# virtio 1.0
+ 1044 Virtio RNG
+# virtio 1.0
+ 1045 Virtio memory balloon
+# virtio 1.0
+ 1048 Virtio SCSI
+# virtio 1.0
+ 1049 Virtio filesystem
+# virtio 1.0
+ 1050 Virtio GPU
+# virtio 1.0
+ 1052 Virtio input
+# virtio 1.0
+ 1053 Virtio socket
+ 105a Virtio file system
+ 1110 Inter-VM shared memory
+ 1af4 1100 QEMU Virtual Machine
+1b73 Fresco Logic
+ 1000 FL1000G USB 3.0 Host Controller
+ 1d5c 1000 Anker USB 3.0 Express Card
+ 1009 FL1009 USB 3.0 Host Controller
+ 1100 FL1100 USB 3.0 Host Controller
+ 16b8 6e31 Allegro Pro USB 3.0 PCIe
diff --git a/shared/config/task_profiles.json b/shared/config/task_profiles.json
index 42366fc..719d352 100644
--- a/shared/config/task_profiles.json
+++ b/shared/config/task_profiles.json
@@ -27,6 +27,19 @@
]
},
{
+ "Name": "ServicePerformance",
+ "Actions": [
+ {
+ "Name": "JoinCgroup",
+ "Params":
+ {
+ "Controller": "cpu",
+ "Path": "system-background"
+ }
+ }
+ ]
+ },
+ {
"Name": "HighPerformance",
"Actions": [
{
@@ -253,59 +266,6 @@
},
{
- "Name": "LowIoPriority",
- "Actions": [
- {
- "Name": "JoinCgroup",
- "Params":
- {
- "Controller": "blkio",
- "Path": "background"
- }
- }
- ]
- },
- {
- "Name": "NormalIoPriority",
- "Actions": [
- {
- "Name": "JoinCgroup",
- "Params":
- {
- "Controller": "blkio",
- "Path": ""
- }
- }
- ]
- },
- {
- "Name": "HighIoPriority",
- "Actions": [
- {
- "Name": "JoinCgroup",
- "Params":
- {
- "Controller": "blkio",
- "Path": ""
- }
- }
- ]
- },
- {
- "Name": "MaxIoPriority",
- "Actions": [
- {
- "Name": "JoinCgroup",
- "Params":
- {
- "Controller": "blkio",
- "Path": ""
- }
- }
- ]
- },
-
- {
"Name": "TimerSlackHigh",
"Actions": [
{
@@ -433,6 +393,10 @@
"Profiles": [ "MaxPerformance", "MaxIoPriority", "TimerSlackNormal" ]
},
{
+ "Name": "SCHED_SP_SYSTEM",
+ "Profiles": [ "ServicePerformance", "LowIoPriority", "TimerSlackNormal" ]
+ },
+ {
"Name": "SCHED_SP_RT_APP",
"Profiles": [ "RealtimePerformance", "MaxIoPriority", "TimerSlackNormal" ]
},
diff --git a/shared/config/ueventd.rc b/shared/config/ueventd.rc
index 8f24201..9af7b8d 100644
--- a/shared/config/ueventd.rc
+++ b/shared/config/ueventd.rc
@@ -9,9 +9,8 @@
# resume-on-reboot
/dev/block/pmem0 0770 system system
-# vtpm
-/dev/tpm0 0000 root root
-/dev/tpmrm0 000 system system
+# hwcomposer
+/dev/block/pmem1 0770 system system
# seriallogging
/dev/hvc2 0660 system logd
@@ -26,7 +25,12 @@
/dev/hvc5 0660 bluetooth bluetooth
/dev/vhci 0660 bluetooth bluetooth
+# gnss hal can access hvc6/hvc7
+/dev/hvc6 0666 system system
+/dev/hvc7 0666 system system
+# gnss0/gnss1 are symlinks of hvc6/hvc7
/dev/gnss0 0666 system system
+/dev/gnss1 0666 system system
# Factory Reset Protection
/dev/block/by-name/frp 0660 system system
diff --git a/shared/config/wpa_supplicant.rc b/shared/config/wpa_supplicant.rc
new file mode 100644
index 0000000..50c5faa
--- /dev/null
+++ b/shared/config/wpa_supplicant.rc
@@ -0,0 +1,9 @@
+service wpa_supplicant /vendor/bin/hw/wpa_supplicant \
+ -O/data/vendor/wifi/wpa/sockets -puse_p2p_group_interface=1p2p_device=1 \
+ -m/vendor/etc/wifi/p2p_supplicant.conf \
+ -g@android:wpa_wlan0 -dd
+ interface aidl android.hardware.wifi.supplicant.ISupplicant/default
+ socket wpa_wlan0 dgram 660 wifi wifi
+ group system wifi inet
+ disabled
+ oneshot
diff --git a/shared/device.mk b/shared/device.mk
index ebade7a..04dedbd 100644
--- a/shared/device.mk
+++ b/shared/device.mk
@@ -26,13 +26,27 @@
# Enforce generic ramdisk allow list
$(call inherit-product, $(SRC_TARGET_DIR)/product/generic_ramdisk.mk)
+# Set Vendor SPL to match platform
+VENDOR_SECURITY_PATCH = $(PLATFORM_SECURITY_PATCH)
+
+# Set boot SPL
+BOOT_SECURITY_PATCH = $(PLATFORM_SECURITY_PATCH)
+
+PRODUCT_VENDOR_PROPERTIES += \
+ ro.vendor.boot_security_patch=$(BOOT_SECURITY_PATCH)
+
PRODUCT_SOONG_NAMESPACES += device/generic/goldfish-opengl # for vulkan
PRODUCT_SOONG_NAMESPACES += device/generic/goldfish # for audio and wifi
-PRODUCT_SHIPPING_API_LEVEL := 31
+PRODUCT_SHIPPING_API_LEVEL := 33
PRODUCT_USE_DYNAMIC_PARTITIONS := true
DISABLE_RILD_OEM_HOOK := true
+# TODO(b/205788876) remove this condition when openwrt has an image for arm.
+ifndef PRODUCT_ENFORCE_MAC80211_HWSIM
+PRODUCT_ENFORCE_MAC80211_HWSIM := true
+endif
+
PRODUCT_SET_DEBUGFS_RESTRICTIONS := true
PRODUCT_SOONG_NAMESPACES += device/generic/goldfish-opengl # for vulkan
@@ -46,13 +60,18 @@
TARGET_ENABLE_HOST_BLUETOOTH_EMULATION ?= true
TARGET_USE_BTLINUX_HAL_IMPL ?= true
+# TODO(b/65201432): Swiftshader needs to create executable memory.
+PRODUCT_REQUIRES_INSECURE_EXECMEM_FOR_SWIFTSHADER := true
+
AB_OTA_UPDATER := true
AB_OTA_PARTITIONS += \
boot \
+ init_boot \
odm \
odm_dlkm \
product \
system \
+ system_dlkm \
system_ext \
vbmeta \
vbmeta_system \
@@ -61,7 +80,8 @@
vendor_dlkm \
# Enable Virtual A/B
-$(call inherit-product, $(SRC_TARGET_DIR)/product/virtual_ab_ota/compression.mk)
+$(call inherit-product, $(SRC_TARGET_DIR)/product/virtual_ab_ota/android_t_baseline.mk)
+PRODUCT_VIRTUAL_AB_COMPRESSION_METHOD := gz
# Enable Scoped Storage related
$(call inherit-product, $(SRC_TARGET_DIR)/product/emulated_storage.mk)
@@ -73,24 +93,31 @@
persist.adb.tcp.port=5555 \
ro.com.google.locationfeatures=1 \
persist.sys.fuse.passthrough.enable=true \
+ persist.sys.fuse.bpf.enable=false \
+
+# Until we support adb keys on user builds, and fix logcat over serial,
+# spawn adbd by default without authorization for "adb logcat"
+ifeq ($(TARGET_BUILD_VARIANT),user)
+PRODUCT_PRODUCT_PROPERTIES += \
+ ro.adb.secure=0 \
+ ro.debuggable=1
+endif
# Explanation of specific properties:
-# debug.hwui.swap_with_damage avoids boot failure on M http://b/25152138
-# ro.opengles.version OpenGLES 3.0
# ro.hardware.keystore_desede=true needed for CtsKeystoreTestCases
PRODUCT_VENDOR_PROPERTIES += \
tombstoned.max_tombstone_count=500 \
vendor.bt.rootcanal_test_console=off \
- debug.hwui.swap_with_damage=0 \
ro.carrier=unknown \
ro.com.android.dataroaming?=false \
ro.hardware.virtual_device=1 \
ro.logd.size=1M \
- ro.opengles.version=196608 \
wifi.interface=wlan0 \
+ wifi.direct.interface=p2p-dev-wlan0 \
persist.sys.zram_enabled=1 \
ro.hardware.keystore_desede=true \
ro.rebootescrow.device=/dev/block/pmem0 \
+ ro.vendor.hwcomposer.pmem=/dev/block/pmem1 \
ro.incremental.enable=1 \
debug.c2.use_dmabufheaps=1 \
ro.camerax.extensions.enabled=true \
@@ -157,23 +184,14 @@
PRODUCT_PACKAGES += \
CuttlefishService \
cuttlefish_sensor_injection \
- rename_netiface \
- setup_wifi \
- bt_vhci_forwarder \
socket_vsock_proxy \
tombstone_transmit \
tombstone_producer \
suspend_blocker \
vsoc_input_service \
- vtpm_manager \
-SOONG_CONFIG_NAMESPACES += cvd
-SOONG_CONFIG_cvd += launch_configs
-SOONG_CONFIG_cvd_launch_configs += \
- cvd_config_auto.json \
- cvd_config_phone.json \
- cvd_config_tablet.json \
- cvd_config_tv.json \
+$(call soong_config_append,cvd,launch_configs,cvd_config_auto.json cvd_config_foldable.json cvd_config_go.json cvd_config_phone.json cvd_config_slim.json cvd_config_tablet.json cvd_config_tv.json cvd_config_wear.json)
+$(call soong_config_append,cvd,grub_config,grub.cfg)
#
# Packages for AOSP-available stuff we use from the framework
@@ -183,7 +201,6 @@
ip \
sleep \
tcpdump \
- wpa_supplicant \
wificond \
#
@@ -196,12 +213,6 @@
libGLESv1_CM_angle \
libGLESv2_angle
-# SwiftShader provides a software-only implementation that is not thread-safe
-PRODUCT_PACKAGES += \
- libEGL_swiftshader \
- libGLESv1_CM_swiftshader \
- libGLESv2_swiftshader
-
# GL implementation for virgl
PRODUCT_PACKAGES += \
libGLES_mesa \
@@ -218,7 +229,6 @@
# GL/Vk implementation for gfxstream
PRODUCT_PACKAGES += \
- hwcomposer.ranchu \
libandroidemu \
libOpenglCodecCommon \
libOpenglSystemCommon \
@@ -235,9 +245,19 @@
#
PRODUCT_PACKAGES += \
aidl_lazy_test_server \
- hidl_lazy_test_server
+ aidl_lazy_cb_test_server \
+ hidl_lazy_test_server \
+ hidl_lazy_cb_test_server
-DEVICE_PACKAGE_OVERLAYS := device/google/cuttlefish/shared/overlay
+# Runtime Resource Overlays
+ifneq ($(LOCAL_PREFER_VENDOR_APEX),true)
+PRODUCT_PACKAGES += \
+ cuttlefish_overlay_connectivity \
+ cuttlefish_overlay_frameworks_base_core \
+ cuttlefish_overlay_settings_provider
+
+endif
+
# PRODUCT_AAPT_CONFIG and PRODUCT_AAPT_PREF_CONFIG are intentionally not set to
# pick up every density resources.
@@ -252,6 +272,7 @@
ifneq ($(LOCAL_SENSOR_FILE_OVERRIDES),true)
+ifneq ($(LOCAL_PREFER_VENDOR_APEX),true)
PRODUCT_COPY_FILES += \
frameworks/native/data/etc/android.hardware.sensor.ambient_temperature.xml:$(TARGET_COPY_OUT_VENDOR)/etc/permissions/android.hardware.sensor.ambient_temperature.xml \
frameworks/native/data/etc/android.hardware.sensor.barometer.xml:$(TARGET_COPY_OUT_VENDOR)/etc/permissions/android.hardware.sensor.barometer.xml \
@@ -261,30 +282,12 @@
frameworks/native/data/etc/android.hardware.sensor.proximity.xml:$(TARGET_COPY_OUT_VENDOR)/etc/permissions/android.hardware.sensor.proximity.xml \
frameworks/native/data/etc/android.hardware.sensor.relative_humidity.xml:$(TARGET_COPY_OUT_VENDOR)/etc/permissions/android.hardware.sensor.relative_humidity.xml
endif
+endif
+ifneq ($(LOCAL_PREFER_VENDOR_APEX),true)
PRODUCT_COPY_FILES += \
- hardware/google/camera/devices/EmulatedCamera/hwl/configs/emu_camera_back.json:$(TARGET_COPY_OUT_VENDOR)/etc/config/emu_camera_back.json \
- hardware/google/camera/devices/EmulatedCamera/hwl/configs/emu_camera_front.json:$(TARGET_COPY_OUT_VENDOR)/etc/config/emu_camera_front.json \
- hardware/google/camera/devices/EmulatedCamera/hwl/configs/emu_camera_depth.json:$(TARGET_COPY_OUT_VENDOR)/etc/config/emu_camera_depth.json \
- device/google/cuttlefish/shared/config/init.vendor.rc:$(TARGET_COPY_OUT_VENDOR)/etc/init/hw/init.cutf_cvm.rc \
- device/google/cuttlefish/shared/config/init.product.rc:$(TARGET_COPY_OUT_PRODUCT)/etc/init/init.rc \
- device/google/cuttlefish/shared/config/ueventd.rc:$(TARGET_COPY_OUT_VENDOR)/ueventd.rc \
- device/google/cuttlefish/shared/config/media_codecs.xml:$(TARGET_COPY_OUT_VENDOR)/etc/media_codecs.xml \
- device/google/cuttlefish/shared/config/media_codecs_google_video.xml:$(TARGET_COPY_OUT_VENDOR)/etc/media_codecs_google_video.xml \
- device/google/cuttlefish/shared/config/media_codecs_performance.xml:$(TARGET_COPY_OUT_VENDOR)/etc/media_codecs_performance.xml \
- device/google/cuttlefish/shared/config/media_profiles.xml:$(TARGET_COPY_OUT_VENDOR)/etc/media_profiles_V1_0.xml \
device/google/cuttlefish/shared/permissions/cuttlefish_excluded_hardware.xml:$(TARGET_COPY_OUT_VENDOR)/etc/permissions/cuttlefish_excluded_hardware.xml \
- device/google/cuttlefish/shared/permissions/privapp-permissions-cuttlefish.xml:$(TARGET_COPY_OUT_VENDOR)/etc/permissions/privapp-permissions-cuttlefish.xml \
- frameworks/av/media/libeffects/data/audio_effects.xml:$(TARGET_COPY_OUT_VENDOR)/etc/audio_effects.xml \
- frameworks/av/media/libstagefright/data/media_codecs_google_audio.xml:$(TARGET_COPY_OUT_VENDOR)/etc/media_codecs_google_audio.xml \
- frameworks/av/media/libstagefright/data/media_codecs_google_telephony.xml:$(TARGET_COPY_OUT_VENDOR)/etc/media_codecs_google_telephony.xml \
- frameworks/av/services/audiopolicy/config/r_submix_audio_policy_configuration.xml:$(TARGET_COPY_OUT_VENDOR)/etc/r_submix_audio_policy_configuration.xml \
- frameworks/av/services/audiopolicy/config/audio_policy_volumes.xml:$(TARGET_COPY_OUT_VENDOR)/etc/audio_policy_volumes.xml \
- frameworks/av/services/audiopolicy/config/default_volume_tables.xml:$(TARGET_COPY_OUT_VENDOR)/etc/default_volume_tables.xml \
- frameworks/av/services/audiopolicy/config/surround_sound_configuration_5_0.xml:$(TARGET_COPY_OUT_VENDOR)/etc/surround_sound_configuration_5_0.xml \
frameworks/native/data/etc/android.hardware.audio.low_latency.xml:$(TARGET_COPY_OUT_VENDOR)/etc/permissions/android.hardware.audio.low_latency.xml \
- frameworks/native/data/etc/android.hardware.bluetooth.xml:$(TARGET_COPY_OUT_VENDOR)/etc/permissions/android.hardware.bluetooth.xml \
- frameworks/native/data/etc/android.hardware.bluetooth_le.xml:$(TARGET_COPY_OUT_VENDOR)/etc/permissions/android.hardware.bluetooth_le.xml \
frameworks/native/data/etc/android.hardware.camera.concurrent.xml:$(TARGET_COPY_OUT_VENDOR)/etc/permissions/android.hardware.camera.concurrent.xml \
frameworks/native/data/etc/android.hardware.camera.flash-autofocus.xml:$(TARGET_COPY_OUT_VENDOR)/etc/permissions/android.hardware.camera.flash-autofocus.xml \
frameworks/native/data/etc/android.hardware.camera.front.xml:$(TARGET_COPY_OUT_VENDOR)/etc/permissions/android.hardware.camera.front.xml \
@@ -296,45 +299,62 @@
frameworks/native/data/etc/android.hardware.usb.accessory.xml:$(TARGET_COPY_OUT_VENDOR)/etc/permissions/android.hardware.usb.accessory.xml \
frameworks/native/data/etc/android.hardware.usb.host.xml:$(TARGET_COPY_OUT_VENDOR)/etc/permissions/android.hardware.usb.host.xml \
frameworks/native/data/etc/android.hardware.wifi.xml:$(TARGET_COPY_OUT_VENDOR)/etc/permissions/android.hardware.wifi.xml \
+ frameworks/native/data/etc/android.hardware.wifi.direct.xml:$(TARGET_COPY_OUT_VENDOR)/etc/permissions/android.hardware.wifi.direct.xml \
frameworks/native/data/etc/android.hardware.wifi.passpoint.xml:$(TARGET_COPY_OUT_VENDOR)/etc/permissions/android.hardware.wifi.passpoint.xml \
frameworks/native/data/etc/android.software.ipsec_tunnels.xml:$(TARGET_COPY_OUT_VENDOR)/etc/permissions/android.software.ipsec_tunnels.xml \
frameworks/native/data/etc/android.software.sip.voip.xml:$(TARGET_COPY_OUT_VENDOR)/etc/permissions/android.software.sip.voip.xml \
frameworks/native/data/etc/android.software.verified_boot.xml:$(TARGET_COPY_OUT_VENDOR)/etc/permissions/android.software.verified_boot.xml \
- system/bt/vendor_libs/test_vendor_lib/data/controller_properties.json:vendor/etc/bluetooth/controller_properties.json \
+ hardware/google/camera/devices/EmulatedCamera/hwl/configs/emu_camera_back.json:$(TARGET_COPY_OUT_VENDOR)/etc/config/emu_camera_back.json \
+ hardware/google/camera/devices/EmulatedCamera/hwl/configs/emu_camera_front.json:$(TARGET_COPY_OUT_VENDOR)/etc/config/emu_camera_front.json \
+ hardware/google/camera/devices/EmulatedCamera/hwl/configs/emu_camera_depth.json:$(TARGET_COPY_OUT_VENDOR)/etc/config/emu_camera_depth.json
+endif
+PRODUCT_COPY_FILES += \
+ frameworks/native/data/etc/android.hardware.consumerir.xml:$(TARGET_COPY_OUT_VENDOR)/etc/permissions/android.hardware.consumerir.xml \
+ device/google/cuttlefish/shared/config/init.vendor.rc:$(TARGET_COPY_OUT_VENDOR)/etc/init/init.cutf_cvm.rc \
+ device/google/cuttlefish/shared/config/init.product.rc:$(TARGET_COPY_OUT_PRODUCT)/etc/init/init.rc \
+ device/google/cuttlefish/shared/config/ueventd.rc:$(TARGET_COPY_OUT_VENDOR)/etc/ueventd.rc \
+ device/google/cuttlefish/shared/config/media_codecs.xml:$(TARGET_COPY_OUT_VENDOR)/etc/media_codecs.xml \
+ device/google/cuttlefish/shared/config/media_codecs_google_video.xml:$(TARGET_COPY_OUT_VENDOR)/etc/media_codecs_google_video.xml \
+ device/google/cuttlefish/shared/config/media_codecs_performance.xml:$(TARGET_COPY_OUT_VENDOR)/etc/media_codecs_performance.xml \
+ device/google/cuttlefish/shared/config/media_profiles.xml:$(TARGET_COPY_OUT_VENDOR)/etc/media_profiles_V1_0.xml \
+ device/google/cuttlefish/shared/permissions/privapp-permissions-cuttlefish.xml:$(TARGET_COPY_OUT_VENDOR)/etc/permissions/privapp-permissions-cuttlefish.xml \
+ frameworks/av/media/libeffects/data/audio_effects.xml:$(TARGET_COPY_OUT_VENDOR)/etc/audio_effects.xml \
+ frameworks/av/media/libstagefright/data/media_codecs_google_audio.xml:$(TARGET_COPY_OUT_VENDOR)/etc/media_codecs_google_audio.xml \
+ frameworks/av/media/libstagefright/data/media_codecs_google_telephony.xml:$(TARGET_COPY_OUT_VENDOR)/etc/media_codecs_google_telephony.xml \
+ frameworks/av/services/audiopolicy/config/a2dp_in_audio_policy_configuration_7_0.xml:$(TARGET_COPY_OUT_VENDOR)/etc/a2dp_in_audio_policy_configuration_7_0.xml \
+ frameworks/av/services/audiopolicy/config/bluetooth_audio_policy_configuration_7_0.xml:$(TARGET_COPY_OUT_VENDOR)/etc/bluetooth_audio_policy_configuration_7_0.xml \
+ frameworks/av/services/audiopolicy/config/r_submix_audio_policy_configuration.xml:$(TARGET_COPY_OUT_VENDOR)/etc/r_submix_audio_policy_configuration.xml \
+ frameworks/av/services/audiopolicy/config/audio_policy_volumes.xml:$(TARGET_COPY_OUT_VENDOR)/etc/audio_policy_volumes.xml \
+ frameworks/av/services/audiopolicy/config/default_volume_tables.xml:$(TARGET_COPY_OUT_VENDOR)/etc/default_volume_tables.xml \
+ frameworks/av/services/audiopolicy/config/surround_sound_configuration_5_0.xml:$(TARGET_COPY_OUT_VENDOR)/etc/surround_sound_configuration_5_0.xml \
device/google/cuttlefish/shared/config/task_profiles.json:$(TARGET_COPY_OUT_VENDOR)/etc/task_profiles.json \
+
+ifeq ($(LOCAL_PREFER_VENDOR_APEX),true)
+PRODUCT_PACKAGES += com.google.cf.input.config
+else
+PRODUCT_COPY_FILES += \
device/google/cuttlefish/shared/config/input/Crosvm_Virtio_Multitouch_Touchscreen_0.idc:$(TARGET_COPY_OUT_VENDOR)/usr/idc/Crosvm_Virtio_Multitouch_Touchscreen_0.idc \
device/google/cuttlefish/shared/config/input/Crosvm_Virtio_Multitouch_Touchscreen_1.idc:$(TARGET_COPY_OUT_VENDOR)/usr/idc/Crosvm_Virtio_Multitouch_Touchscreen_1.idc \
device/google/cuttlefish/shared/config/input/Crosvm_Virtio_Multitouch_Touchscreen_2.idc:$(TARGET_COPY_OUT_VENDOR)/usr/idc/Crosvm_Virtio_Multitouch_Touchscreen_2.idc \
device/google/cuttlefish/shared/config/input/Crosvm_Virtio_Multitouch_Touchscreen_3.idc:$(TARGET_COPY_OUT_VENDOR)/usr/idc/Crosvm_Virtio_Multitouch_Touchscreen_3.idc
+endif
-ifeq ($(TARGET_RO_FILE_SYSTEM_TYPE),ext4)
PRODUCT_COPY_FILES += \
device/google/cuttlefish/shared/config/fstab.f2fs:$(TARGET_COPY_OUT_VENDOR_RAMDISK)/first_stage_ramdisk/fstab.f2fs \
- device/google/cuttlefish/shared/config/fstab.f2fs:$(TARGET_COPY_OUT_VENDOR_RAMDISK)/fstab.f2fs \
device/google/cuttlefish/shared/config/fstab.f2fs:$(TARGET_COPY_OUT_VENDOR)/etc/fstab.f2fs \
device/google/cuttlefish/shared/config/fstab.f2fs:$(TARGET_COPY_OUT_RECOVERY)/root/first_stage_ramdisk/fstab.f2fs \
device/google/cuttlefish/shared/config/fstab.ext4:$(TARGET_COPY_OUT_VENDOR_RAMDISK)/first_stage_ramdisk/fstab.ext4 \
- device/google/cuttlefish/shared/config/fstab.ext4:$(TARGET_COPY_OUT_VENDOR_RAMDISK)/fstab.ext4 \
device/google/cuttlefish/shared/config/fstab.ext4:$(TARGET_COPY_OUT_VENDOR)/etc/fstab.ext4 \
device/google/cuttlefish/shared/config/fstab.ext4:$(TARGET_COPY_OUT_RECOVERY)/root/first_stage_ramdisk/fstab.ext4
-else
-PRODUCT_COPY_FILES += \
- device/google/cuttlefish/shared/config/fstab-$(TARGET_RO_FILE_SYSTEM_TYPE).f2fs:$(TARGET_COPY_OUT_VENDOR_RAMDISK)/first_stage_ramdisk/fstab.f2fs \
- device/google/cuttlefish/shared/config/fstab-$(TARGET_RO_FILE_SYSTEM_TYPE).f2fs:$(TARGET_COPY_OUT_VENDOR_RAMDISK)/fstab.f2fs \
- device/google/cuttlefish/shared/config/fstab-$(TARGET_RO_FILE_SYSTEM_TYPE).f2fs:$(TARGET_COPY_OUT_VENDOR)/etc/fstab.f2fs \
- device/google/cuttlefish/shared/config/fstab-$(TARGET_RO_FILE_SYSTEM_TYPE).f2fs:$(TARGET_COPY_OUT_RECOVERY)/root/first_stage_ramdisk/fstab.f2fs \
- device/google/cuttlefish/shared/config/fstab-$(TARGET_RO_FILE_SYSTEM_TYPE).ext4:$(TARGET_COPY_OUT_VENDOR_RAMDISK)/first_stage_ramdisk/fstab.ext4 \
- device/google/cuttlefish/shared/config/fstab-$(TARGET_RO_FILE_SYSTEM_TYPE).ext4:$(TARGET_COPY_OUT_VENDOR_RAMDISK)/fstab.ext4 \
- device/google/cuttlefish/shared/config/fstab-$(TARGET_RO_FILE_SYSTEM_TYPE).ext4:$(TARGET_COPY_OUT_VENDOR)/etc/fstab.ext4 \
- device/google/cuttlefish/shared/config/fstab-$(TARGET_RO_FILE_SYSTEM_TYPE).ext4:$(TARGET_COPY_OUT_RECOVERY)/root/first_stage_ramdisk/fstab.ext4
-endif
ifeq ($(TARGET_VULKAN_SUPPORT),true)
+ifneq ($(LOCAL_PREFER_VENDOR_APEX),true)
PRODUCT_COPY_FILES += \
frameworks/native/data/etc/android.hardware.vulkan.level-0.xml:$(TARGET_COPY_OUT_VENDOR)/etc/permissions/android.hardware.vulkan.level.xml \
frameworks/native/data/etc/android.hardware.vulkan.version-1_0_3.xml:$(TARGET_COPY_OUT_VENDOR)/etc/permissions/android.hardware.vulkan.version.xml \
- frameworks/native/data/etc/android.software.vulkan.deqp.level-2021-03-01.xml:$(TARGET_COPY_OUT_VENDOR)/etc/permissions/android.software.vulkan.deqp.level.xml \
- frameworks/native/data/etc/android.software.opengles.deqp.level-2021-03-01.xml:$(TARGET_COPY_OUT_VENDOR)/etc/permissions/android.software.opengles.deqp.level.xml
+ frameworks/native/data/etc/android.software.vulkan.deqp.level-2022-03-01.xml:$(TARGET_COPY_OUT_VENDOR)/etc/permissions/android.software.vulkan.deqp.level.xml \
+ frameworks/native/data/etc/android.software.opengles.deqp.level-2022-03-01.xml:$(TARGET_COPY_OUT_VENDOR)/etc/permissions/android.software.opengles.deqp.level.xml
+endif
endif
# Packages for HAL implementations
@@ -352,6 +372,12 @@
android.hardware.weaver-service.example
#
+# IR aidl HAL
+#
+PRODUCT_PACKAGES += \
+ android.hardware.ir-service.example
+
+#
# OemLock aidl HAL
#
PRODUCT_PACKAGES += \
@@ -371,22 +397,32 @@
#
# Hardware Composer HAL
#
+# The device needs to avoid having both hwcomposer2.4 and hwcomposer3
+# services running at the same time so make the user manually enables
+# in order to run with --gpu_mode=drm.
+ifeq ($(TARGET_ENABLE_DRMHWCOMPOSER),true)
+DEVICE_MANIFEST_FILE += \
+ device/google/cuttlefish/shared/config/[email protected]
+
PRODUCT_PACKAGES += \
- hwcomposer.drm_minigbm \
- hwcomposer.cutf \
- hwcomposer-stats \
- [email protected]
+ [email protected] \
+ hwcomposer.drm
+else
+PRODUCT_PACKAGES += \
+ android.hardware.graphics.composer3-service.ranchu
+endif
#
# Gralloc HAL
#
PRODUCT_PACKAGES += \
- [email protected] \
+ android.hardware.graphics.allocator-V1-service.minigbm \
[email protected]
#
# Bluetooth HAL and Compatibility Bluetooth library (for older revs).
#
+ifneq ($(LOCAL_PREFER_VENDOR_APEX),true)
ifeq ($(LOCAL_BLUETOOTH_PRODUCT_PACKAGE),)
ifeq ($(TARGET_ENABLE_HOST_BLUETOOTH_EMULATION),true)
ifeq ($(TARGET_USE_BTLINUX_HAL_IMPL),true)
@@ -400,27 +436,49 @@
DEVICE_MANIFEST_FILE += device/google/cuttlefish/shared/config/[email protected]
endif
+PRODUCT_COPY_FILES +=\
+ frameworks/native/data/etc/android.hardware.bluetooth.xml:$(TARGET_COPY_OUT_VENDOR)/etc/permissions/android.hardware.bluetooth.xml \
+ frameworks/native/data/etc/android.hardware.bluetooth_le.xml:$(TARGET_COPY_OUT_VENDOR)/etc/permissions/android.hardware.bluetooth_le.xml
+
PRODUCT_PACKAGES += $(LOCAL_BLUETOOTH_PRODUCT_PACKAGE)
-PRODUCT_PACKAGES += [email protected]
+PRODUCT_PACKAGES += [email protected] bt_vhci_forwarder
+
+# Bluetooth initialization configuration is copied to the init folder here instead of being added
+# as an init_rc attribute of the bt_vhci_forward binary. The bt_vhci_forward binary is used by
+# multiple targets with different initialization configurations.
+PRODUCT_COPY_FILES += \
+ device/google/cuttlefish/guest/commands/bt_vhci_forwarder/bt_vhci_forwarder.rc:$(TARGET_COPY_OUT_VENDOR)/etc/init/bt_vhci_forwarder.rc
+
+else
+PRODUCT_PACKAGES += com.google.cf.bt [email protected]
+endif
+
+#
+# Bluetooth Audio AIDL HAL
+#
+PRODUCT_PACKAGES += \
+ android.hardware.bluetooth.audio-impl \
#
# Audio HAL
#
-LOCAL_AUDIO_PRODUCT_PACKAGE ?= \
+ifndef LOCAL_AUDIO_PRODUCT_PACKAGE
+LOCAL_AUDIO_PRODUCT_PACKAGE := \
android.hardware.audio.service \
- [email protected] \
- [email protected] \
+ [email protected] \
+ [email protected]
+endif
-LOCAL_AUDIO_PRODUCT_COPY_FILES ?= \
+ifndef LOCAL_AUDIO_PRODUCT_COPY_FILES
+LOCAL_AUDIO_PRODUCT_COPY_FILES := \
device/generic/goldfish/audio/policy/audio_policy_configuration.xml:$(TARGET_COPY_OUT_VENDOR)/etc/audio_policy_configuration.xml \
device/generic/goldfish/audio/policy/primary_audio_policy_configuration.xml:$(TARGET_COPY_OUT_VENDOR)/etc/primary_audio_policy_configuration.xml \
frameworks/av/services/audiopolicy/config/r_submix_audio_policy_configuration.xml:$(TARGET_COPY_OUT_VENDOR)/etc/r_submix_audio_policy_configuration.xml \
frameworks/av/services/audiopolicy/config/audio_policy_volumes.xml:$(TARGET_COPY_OUT_VENDOR)/etc/audio_policy_volumes.xml \
frameworks/av/services/audiopolicy/config/default_volume_tables.xml:$(TARGET_COPY_OUT_VENDOR)/etc/default_volume_tables.xml \
- frameworks/av/media/libeffects/data/audio_effects.xml:$(TARGET_COPY_OUT_VENDOR)/etc/audio_effects.xml \
-
-LOCAL_AUDIO_DEVICE_PACKAGE_OVERLAYS ?=
+ frameworks/av/media/libeffects/data/audio_effects.xml:$(TARGET_COPY_OUT_VENDOR)/etc/audio_effects.xml
+endif
PRODUCT_PACKAGES += $(LOCAL_AUDIO_PRODUCT_PACKAGE)
PRODUCT_COPY_FILES += $(LOCAL_AUDIO_PRODUCT_COPY_FILES)
@@ -454,20 +512,28 @@
# Contexthub HAL
#
PRODUCT_PACKAGES += \
- [email protected]
+ android.hardware.contexthub-service.example
#
# Drm HAL
#
PRODUCT_PACKAGES += \
- [email protected] \
- [email protected]
+ [email protected] \
+ [email protected]
+
+#
+# Confirmation UI HAL
+#
+ifeq ($(LOCAL_CONFIRMATIONUI_PRODUCT_PACKAGE),)
+ LOCAL_CONFIRMATIONUI_PRODUCT_PACKAGE := [email protected]
+endif
+PRODUCT_PACKAGES += $(LOCAL_CONFIRMATIONUI_PRODUCT_PACKAGE)
#
# Dumpstate HAL
#
ifeq ($(LOCAL_DUMPSTATE_PRODUCT_PACKAGE),)
- LOCAL_DUMPSTATE_PRODUCT_PACKAGE := [email protected]
+ LOCAL_DUMPSTATE_PRODUCT_PACKAGE += android.hardware.dumpstate-service.example
endif
PRODUCT_PACKAGES += $(LOCAL_DUMPSTATE_PRODUCT_PACKAGE)
@@ -481,6 +547,10 @@
DEVICE_MANIFEST_FILE += \
device/google/cuttlefish/guest/hals/camera/manifest.xml
else
+ifeq ($(LOCAL_PREFER_VENDOR_APEX),true)
+PRODUCT_PACKAGES += com.google.emulated.camera.provider.hal
+PRODUCT_PACKAGES += com.google.emulated.camera.provider.hal.fastscenecycle
+endif
PRODUCT_PACKAGES += \
[email protected] \
libgooglecamerahwl_impl \
@@ -491,7 +561,7 @@
# Gatekeeper
#
ifeq ($(LOCAL_GATEKEEPER_PRODUCT_PACKAGE),)
- LOCAL_GATEKEEPER_PRODUCT_PACKAGE := [email protected]
+ LOCAL_GATEKEEPER_PRODUCT_PACKAGE := [email protected]
endif
PRODUCT_PACKAGES += \
$(LOCAL_GATEKEEPER_PRODUCT_PACKAGE)
@@ -507,8 +577,9 @@
# Health
ifeq ($(LOCAL_HEALTH_PRODUCT_PACKAGE),)
LOCAL_HEALTH_PRODUCT_PACKAGE := \
- [email protected] \
- [email protected]
+ android.hardware.health-service.cuttlefish \
+ android.hardware.health-service.cuttlefish_recovery \
+
endif
PRODUCT_PACKAGES += $(LOCAL_HEALTH_PRODUCT_PACKAGE)
@@ -518,25 +589,36 @@
# Identity Credential
PRODUCT_PACKAGES += \
- android.hardware.identity-service.example
+ android.hardware.identity-service.remote
-# Input Classifier HAL
PRODUCT_PACKAGES += \
- [email protected]
+ android.hardware.input.processor-service.example
+
+# Netlink Interceptor HAL
+PRODUCT_PACKAGES += \
+ android.hardware.net.nlinterceptor-service.default
#
# Sensors
#
ifeq ($(LOCAL_SENSOR_PRODUCT_PACKAGE),)
- LOCAL_SENSOR_PRODUCT_PACKAGE := [email protected]
+# TODO(b/210883464): Convert the sensors APEX to use the new AIDL impl.
+#ifeq ($(LOCAL_PREFER_VENDOR_APEX),true)
+# LOCAL_SENSOR_PRODUCT_PACKAGE := com.android.hardware.sensors
+#else
+ LOCAL_SENSOR_PRODUCT_PACKAGE := android.hardware.sensors-service.example
+#endif
endif
PRODUCT_PACKAGES += \
$(LOCAL_SENSOR_PRODUCT_PACKAGE)
#
# Thermal (mock)
#
-PRODUCT_PACKAGES += \
- [email protected]
+ifeq ($(LOCAL_PREFER_VENDOR_APEX),true)
+PRODUCT_PACKAGES += com.android.hardware.thermal.mock
+else
+PRODUCT_PACKAGES += [email protected]
+endif
#
# Lights
@@ -548,51 +630,66 @@
# KeyMint HAL
#
ifeq ($(LOCAL_KEYMINT_PRODUCT_PACKAGE),)
- LOCAL_KEYMINT_PRODUCT_PACKAGE := android.hardware.security.keymint-service
+ LOCAL_KEYMINT_PRODUCT_PACKAGE := android.hardware.security.keymint-service.remote
+# Indicate that this KeyMint includes support for the ATTEST_KEY key purpose.
+PRODUCT_COPY_FILES += \
+ frameworks/native/data/etc/android.hardware.keystore.app_attest_key.xml:$(TARGET_COPY_OUT_VENDOR)/etc/permissions/android.hardware.keystore.app_attest_key.xml
endif
PRODUCT_PACKAGES += \
$(LOCAL_KEYMINT_PRODUCT_PACKAGE)
# Keymint configuration
+ifneq ($(LOCAL_PREFER_VENDOR_APEX),true)
PRODUCT_COPY_FILES += \
frameworks/native/data/etc/android.software.device_id_attestation.xml:$(TARGET_COPY_OUT_VENDOR)/etc/permissions/android.software.device_id_attestation.xml
+endif
#
-# Power HAL
+# Dice HAL
#
PRODUCT_PACKAGES += \
- android.hardware.power-service.example
+ android.hardware.security.dice-service.non-secure-software
#
-# PowerStats HAL
+# Power and PowerStats HALs
#
+ifeq ($(LOCAL_PREFER_VENDOR_APEX),true)
+PRODUCT_PACKAGES += com.android.hardware.power
+else
PRODUCT_PACKAGES += \
- android.hardware.power.stats-service.example
+ android.hardware.power-service.example \
+ android.hardware.power.stats-service.example \
+
+endif
#
# NeuralNetworks HAL
#
PRODUCT_PACKAGES += \
[email protected] \
- [email protected] \
- [email protected] \
- [email protected] \
- [email protected] \
+ [email protected] \
android.hardware.neuralnetworks-service-sample-all \
- android.hardware.neuralnetworks-service-sample-float-fast \
- android.hardware.neuralnetworks-service-sample-float-slow \
- android.hardware.neuralnetworks-service-sample-minimal \
- android.hardware.neuralnetworks-service-sample-quant \
+ android.hardware.neuralnetworks-service-sample-limited \
android.hardware.neuralnetworks-shim-service-sample
#
# USB
+# TODO(b/227791019): Convert USB AIDL HAL to APEX
+# ifeq ($(LOCAL_PREFER_VENDOR_APEX),true)
+# PRODUCT_PACKAGES += \
+# com.android.hardware.usb
+#else
PRODUCT_PACKAGES += \
- [email protected]
+ android.hardware.usb-service.example
+#endif
# Vibrator HAL
+ifeq ($(LOCAL_PREFER_VENDOR_APEX),true)
+PRODUCT_PACKAGES += com.android.hardware.vibrator
+else
PRODUCT_PACKAGES += \
android.hardware.vibrator-service.example
+endif
# BootControl HAL
PRODUCT_PACKAGES += \
@@ -608,26 +705,6 @@
PRODUCT_PACKAGES += \
android.hardware.memtrack-service.example
-# GKI APEX
-# Keep in sync with BOARD_KERNEL_MODULE_INTERFACE_VERSIONS
-ifneq (,$(TARGET_KERNEL_USE))
- ifneq (,$(filter 5.4, $(TARGET_KERNEL_USE)))
- PRODUCT_PACKAGES += com.android.gki.kmi_5_4_android12_unstable
- else
- PRODUCT_PACKAGES += com.android.gki.kmi_$(subst .,_,$(TARGET_KERNEL_USE))_android12_unstable
- endif
-endif
-
-# Prevent GKI and boot image downgrades
-PRODUCT_PRODUCT_PROPERTIES += \
- ro.build.ab_update.gki.prevent_downgrade_version=true \
- ro.build.ab_update.gki.prevent_downgrade_spl=true \
-
-# WLAN driver configuration files
-PRODUCT_COPY_FILES += \
- external/wpa_supplicant_8/wpa_supplicant/wpa_supplicant_template.conf:$(TARGET_COPY_OUT_VENDOR)/etc/wifi/wpa_supplicant.conf \
- $(LOCAL_PATH)/config/wpa_supplicant_overlay.conf:$(TARGET_COPY_OUT_VENDOR)/etc/wifi/wpa_supplicant_overlay.conf
-
# Fastboot HAL & fastbootd
PRODUCT_PACKAGES += \
[email protected] \
@@ -653,16 +730,88 @@
PRODUCT_PACKAGES += linker.recovery shell_and_utilities_recovery
endif
+# wifi
+ifeq ($(LOCAL_PREFER_VENDOR_APEX),true)
+ifneq ($(PRODUCT_ENFORCE_MAC80211_HWSIM),true)
+PRODUCT_PACKAGES += com.google.cf.wifi
+# Demonstrate multi-installed vendor APEXes by installing another wifi HAL vendor APEX
+# which does not include the passpoint feature XML.
#
-# Shell script Vendor Module Loading
-#
+# The default is set in BoardConfig.mk using bootconfig.
+# This can be changed at CVD launch-time using
+# --extra_bootconfig_args "androidboot.vendor.apex.com.android.wifi.hal:=X"
+# or post-launch, at runtime using
+# setprop persist.vendor.apex.com.android.wifi.hal X && reboot
+# where X is the name of the APEX file to use.
+PRODUCT_PACKAGES += com.google.cf.wifi.no-passpoint
+
+$(call add_soong_config_namespace, wpa_supplicant)
+$(call add_soong_config_var_value, wpa_supplicant, platform_version, $(PLATFORM_VERSION))
+$(call add_soong_config_var_value, wpa_supplicant, nl80211_driver, CONFIG_DRIVER_NL80211_QCA)
+PRODUCT_VENDOR_PROPERTIES += ro.vendor.wifi_impl=virt_wifi
+else
+PRODUCT_SOONG_NAMESPACES += device/google/cuttlefish/apex/com.google.cf.wifi_hwsim
+PRODUCT_PACKAGES += com.google.cf.wifi_hwsim
+$(call add_soong_config_namespace, wpa_supplicant)
+$(call add_soong_config_var_value, wpa_supplicant, platform_version, $(PLATFORM_VERSION))
+$(call add_soong_config_var_value, wpa_supplicant, nl80211_driver, CONFIG_DRIVER_NL80211_QCA)
+PRODUCT_VENDOR_PROPERTIES += ro.vendor.wifi_impl=mac8011_hwsim_virtio
+
+$(call soong_config_append,cvdhost,enforce_mac80211_hwsim,true)
+endif
+else
+
+PRODUCT_PACKAGES += \
+ rename_netiface \
+ wpa_supplicant
PRODUCT_COPY_FILES += \
- $(LOCAL_PATH)/config/init.insmod.sh:$(TARGET_COPY_OUT_VENDOR)/bin/init.insmod.sh \
+ device/google/cuttlefish/shared/config/wpa_supplicant.rc:$(TARGET_COPY_OUT_VENDOR)/etc/init/wpa_supplicant.rc
+
+# WLAN driver configuration files
+ifndef LOCAL_WPA_SUPPLICANT_OVERLAY
+LOCAL_WPA_SUPPLICANT_OVERLAY := $(LOCAL_PATH)/config/wpa_supplicant_overlay.conf
+endif
+ifndef LOCAL_P2P_SUPPLICANT
+LOCAL_P2P_SUPPLICANT := $(LOCAL_PATH)/config/p2p_supplicant.conf
+endif
+PRODUCT_COPY_FILES += \
+ external/wpa_supplicant_8/wpa_supplicant/wpa_supplicant_template.conf:$(TARGET_COPY_OUT_VENDOR)/etc/wifi/wpa_supplicant.conf \
+ $(LOCAL_WPA_SUPPLICANT_OVERLAY):$(TARGET_COPY_OUT_VENDOR)/etc/wifi/wpa_supplicant_overlay.conf \
+ $(LOCAL_P2P_SUPPLICANT):$(TARGET_COPY_OUT_VENDOR)/etc/wifi/p2p_supplicant.conf
+
+ifeq ($(PRODUCT_ENFORCE_MAC80211_HWSIM),true)
+PRODUCT_PACKAGES += \
+ mac80211_create_radios \
+ hostapd \
+ [email protected] \
+ init.wifi.sh
+
+PRODUCT_VENDOR_PROPERTIES += ro.vendor.wifi_impl=mac8011_hwsim_virtio
+
+$(call soong_config_append,cvdhost,enforce_mac80211_hwsim,true)
+
+else
+PRODUCT_PACKAGES += setup_wifi
+PRODUCT_VENDOR_PROPERTIES += ro.vendor.wifi_impl=virt_wifi
+endif
+
+endif
+
+# UWB HAL
+PRODUCT_PACKAGES += \
+ android.hardware.uwb-service
+
+ifeq ($(PRODUCT_ENFORCE_MAC80211_HWSIM),true)
+# Wifi Runtime Resource Overlay
+PRODUCT_PACKAGES += \
+ CuttlefishTetheringOverlay \
+ CuttlefishWifiOverlay
+endif
# Host packages to install
PRODUCT_HOST_PACKAGES += socket_vsock_proxy
-PRODUCT_EXTRA_VNDK_VERSIONS := 28 29 30
+PRODUCT_EXTRA_VNDK_VERSIONS := 28 29 30 31
PRODUCT_SOONG_NAMESPACES += external/mesa3d
@@ -674,6 +823,10 @@
PRODUCT_VENDOR_PROPERTIES += \
ro.surface_flinger.running_without_sync_framework=true
+# Enable GPU-intensive background blur support on Cuttlefish when requested by apps
+PRODUCT_VENDOR_PROPERTIES += \
+ ro.surface_flinger.supports_background_blur 1
+
# Set support one-handed mode
PRODUCT_PRODUCT_PROPERTIES += \
ro.support_one_handed_mode=true
@@ -685,3 +838,14 @@
# Set one_handed_mode translate animation duration milliseconds
PRODUCT_PRODUCT_PROPERTIES += \
persist.debug.one_handed_translate_animation_duration=300
+
+# Vendor Dlkm Locader
+PRODUCT_PACKAGES += \
+ dlkm_loader
+
+# NFC AIDL HAL
+PRODUCT_PACKAGES += \
+ android.hardware.nfc-service.cuttlefish
+
+PRODUCT_COPY_FILES += \
+ device/google/cuttlefish/shared/config/pci.ids:$(TARGET_COPY_OUT_VENDOR)/pci.ids
diff --git a/shared/foldable/overlay/frameworks/base/core/res/res/values/config.xml b/shared/foldable/overlay/frameworks/base/core/res/res/values/config.xml
index 918d39a..b0b42ab 100644
--- a/shared/foldable/overlay/frameworks/base/core/res/res/values/config.xml
+++ b/shared/foldable/overlay/frameworks/base/core/res/res/values/config.xml
@@ -32,6 +32,8 @@
</integer-array>
<!-- Indicates whether to enable an animation when unfolding a device or not -->
<bool name="config_unfoldTransitionEnabled">true</bool>
+ <!-- Indicates whether to enable hinge angle sensor when using unfold animation -->
+ <bool name="config_unfoldTransitionHingeAngle">true</bool>
<bool name="config_supportsConcurrentInternalDisplays">false</bool>
<!-- Controls whether the device support multi window modes like split-screen. -->
<bool name="config_supportsMultiWindow">true</bool>
diff --git a/shared/go/OWNERS b/shared/go/OWNERS
new file mode 100644
index 0000000..0c77d0e
--- /dev/null
+++ b/shared/go/OWNERS
@@ -0,0 +1,2 @@
[email protected]
[email protected]
diff --git a/shared/go/android-info.txt b/shared/go/android-info.txt
new file mode 100644
index 0000000..68f4826
--- /dev/null
+++ b/shared/go/android-info.txt
@@ -0,0 +1 @@
+config=go
diff --git a/shared/go/device.mk b/shared/go/device.mk
deleted file mode 100644
index e165923..0000000
--- a/shared/go/device.mk
+++ /dev/null
@@ -1,26 +0,0 @@
-#
-# Copyright (C) 2018 The Android Open Source Project
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-#
-
-$(call inherit-product, build/target/product/go_defaults.mk)
-$(call inherit-product, device/google/cuttlefish/shared/phone/device.mk)
-
-# By default, enable zram; experiment can toggle the flag,
-# which takes effect on boot
-PRODUCT_VENDOR_PROPERTIES += \
- pm.dexopt.downgrade_after_inactive_days=10 \
- pm.dexopt.shared=quicken \
- dalvik.vm.heapgrowthlimit=128m \
- dalvik.vm.heapsize=256m \
diff --git a/shared/go/device_vendor.mk b/shared/go/device_vendor.mk
new file mode 100644
index 0000000..85d64dd
--- /dev/null
+++ b/shared/go/device_vendor.mk
@@ -0,0 +1,47 @@
+#
+# Copyright (C) 2022 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+PRODUCT_MANIFEST_FILES += device/google/cuttlefish/shared/config/product_manifest.xml
+SYSTEM_EXT_MANIFEST_FILES += device/google/cuttlefish/shared/config/system_ext_manifest.xml
+
+$(call inherit-product, $(SRC_TARGET_DIR)/product/handheld_vendor.mk)
+$(call inherit-product, $(SRC_TARGET_DIR)/product/telephony_vendor.mk)
+
+PRODUCT_COPY_FILES += \
+ frameworks/native/data/etc/go_handheld_core_hardware.xml:$(TARGET_COPY_OUT_VENDOR)/etc/permissions/go_handheld_core_hardware.xml
+
+$(call inherit-product, frameworks/native/build/phone-xhdpi-2048-dalvik-heap.mk)
+$(call inherit-product, device/google/cuttlefish/shared/device.mk)
+
+PRODUCT_VENDOR_PROPERTIES += \
+ keyguard.no_require_sim=true \
+ ro.cdma.home.operator.alpha=Android \
+ ro.cdma.home.operator.numeric=302780 \
+ ro.com.android.dataroaming=true \
+ ro.telephony.default_network=9 \
+
+TARGET_USES_CF_RILD ?= true
+ifeq ($(TARGET_USES_CF_RILD),true)
+PRODUCT_PACKAGES += \
+ libcuttlefish-ril-2 \
+ libcuttlefish-rild
+endif
+
+PRODUCT_PACKAGES += \
+ cuttlefish_phone_overlay_frameworks_base_core \
+ cuttlefish_go_phone_overlay_frameworks_base_core \
+
+TARGET_BOARD_INFO_FILE ?= device/google/cuttlefish/shared/go/android-info.txt
diff --git a/shared/go/overlays/core/Android.bp b/shared/go/overlays/core/Android.bp
new file mode 100644
index 0000000..b46bcce
--- /dev/null
+++ b/shared/go/overlays/core/Android.bp
@@ -0,0 +1,8 @@
+package {
+ default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+runtime_resource_overlay {
+ name: "cuttlefish_go_phone_overlay_frameworks_base_core",
+ soc_specific: true,
+}
diff --git a/shared/go/overlays/core/AndroidManifest.xml b/shared/go/overlays/core/AndroidManifest.xml
new file mode 100644
index 0000000..d51da61
--- /dev/null
+++ b/shared/go/overlays/core/AndroidManifest.xml
@@ -0,0 +1,10 @@
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="android.cuttlefish.go_phone.overlay">
+
+ <application android:hasCode="false" />
+
+ <overlay
+ android:targetPackage="android"
+ android:isStatic="true"
+ />
+</manifest>
diff --git a/shared/go/overlays/core/res/values/config.xml b/shared/go/overlays/core/res/values/config.xml
new file mode 100644
index 0000000..da1dca2
--- /dev/null
+++ b/shared/go/overlays/core/res/values/config.xml
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+**
+** Copyright 2022, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+** http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+-->
+<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <!-- True if the device requires AppWidgetService even if it does not have
+ the PackageManager.FEATURE_APP_WIDGETS feature -->
+ <bool name="config_enableAppWidgetService">true</bool>
+
+ <!-- circle shape icon mask to be used for {@link AdaptiveIconDrawable} -->
+ <string name="config_icon_mask" translatable="false">"M50 0C77.6 0 100 22.4 100 50C100 77.6 77.6 100 50 100C22.4 100 0 77.6 0 50C0 22.4 22.4 0 50 0Z"</string>
+</resources>
diff --git a/shared/go_512/device.mk b/shared/go_512/device.mk
deleted file mode 100644
index 0ab601b..0000000
--- a/shared/go_512/device.mk
+++ /dev/null
@@ -1,26 +0,0 @@
-#
-# Copyright (C) 2018 The Android Open Source Project
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-#
-
-$(call inherit-product, build/target/product/go_defaults_512.mk)
-$(call inherit-product, device/google/cuttlefish/shared/phone/device.mk)
-
-# By default, enable zram; experiment can toggle the flag,
-# which takes effect on boot
-PRODUCT_VENDOR_PROPERTIES += \
- pm.dexopt.downgrade_after_inactive_days=10 \
- pm.dexopt.shared=quicken \
- dalvik.vm.heapgrowthlimit=128m \
- dalvik.vm.heapsize=256m \
diff --git a/shared/overlays/SettingsProvider/Android.bp b/shared/overlays/SettingsProvider/Android.bp
new file mode 100644
index 0000000..6072a34
--- /dev/null
+++ b/shared/overlays/SettingsProvider/Android.bp
@@ -0,0 +1,8 @@
+package {
+ default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+runtime_resource_overlay {
+ name: "cuttlefish_overlay_settings_provider",
+ soc_specific: true,
+}
diff --git a/shared/overlays/SettingsProvider/AndroidManifest.xml b/shared/overlays/SettingsProvider/AndroidManifest.xml
new file mode 100644
index 0000000..158fb01
--- /dev/null
+++ b/shared/overlays/SettingsProvider/AndroidManifest.xml
@@ -0,0 +1,11 @@
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.providers.settings.cuttlefish.overlay">
+
+ <application android:hasCode="false" />
+
+ <overlay
+ android:targetPackage="com.android.providers.settings"
+ android:isStatic="true"
+ android:priority="1"
+ />
+</manifest>
diff --git a/shared/overlay/frameworks/base/packages/SettingsProvider/res/values/defaults.xml b/shared/overlays/SettingsProvider/res/values/defaults.xml
similarity index 100%
rename from shared/overlay/frameworks/base/packages/SettingsProvider/res/values/defaults.xml
rename to shared/overlays/SettingsProvider/res/values/defaults.xml
diff --git a/shared/overlays/connectivity/Android.bp b/shared/overlays/connectivity/Android.bp
new file mode 100644
index 0000000..58b2a02
--- /dev/null
+++ b/shared/overlays/connectivity/Android.bp
@@ -0,0 +1,10 @@
+package {
+ default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+runtime_resource_overlay {
+ name: "cuttlefish_overlay_connectivity",
+ resource_dirs: ["res"],
+ vendor: true,
+ sdk_version: "current",
+}
diff --git a/shared/overlays/connectivity/AndroidManifest.xml b/shared/overlays/connectivity/AndroidManifest.xml
new file mode 100644
index 0000000..000f0ba
--- /dev/null
+++ b/shared/overlays/connectivity/AndroidManifest.xml
@@ -0,0 +1,12 @@
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.connectivity.resources.cuttlefish.overlay">
+
+ <application android:hasCode="false" />
+
+ <overlay
+ android:targetPackage="com.android.connectivity.resources"
+ android:targetName="ServiceConnectivityResourcesConfig"
+ android:isStatic="true"
+ android:priority="1"
+ />
+</manifest>
diff --git a/shared/overlays/connectivity/res/values/config.xml b/shared/overlays/connectivity/res/values/config.xml
new file mode 100644
index 0000000..ead8b95
--- /dev/null
+++ b/shared/overlays/connectivity/res/values/config.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+** Copyright 2022, The Android Open Source Project.
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+** http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+-->
+<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <!-- Restricting eth1 for auto/pc/phone/tv -->
+ <string-array translatable="false" name="config_ethernet_interfaces">
+ <item>eth1;11,12,14;;</item>
+ </string-array>
+</resources>
diff --git a/shared/overlays/core/Android.bp b/shared/overlays/core/Android.bp
new file mode 100644
index 0000000..e297776
--- /dev/null
+++ b/shared/overlays/core/Android.bp
@@ -0,0 +1,8 @@
+package {
+ default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+runtime_resource_overlay {
+ name: "cuttlefish_overlay_frameworks_base_core",
+ soc_specific: true,
+}
diff --git a/shared/overlays/core/AndroidManifest.xml b/shared/overlays/core/AndroidManifest.xml
new file mode 100644
index 0000000..a236be9
--- /dev/null
+++ b/shared/overlays/core/AndroidManifest.xml
@@ -0,0 +1,11 @@
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="android.cuttlefish.overlay">
+
+ <application android:hasCode="false" />
+
+ <overlay
+ android:targetPackage="android"
+ android:isStatic="true"
+ android:priority="1"
+ />
+</manifest>
diff --git a/shared/overlay/frameworks/base/core/res/res/xml/power_profile.xml b/shared/overlays/core/res/xml/power_profile.xml
similarity index 100%
rename from shared/overlay/frameworks/base/core/res/res/xml/power_profile.xml
rename to shared/overlays/core/res/xml/power_profile.xml
diff --git a/shared/pc/overlay/frameworks/base/core/res/res/values/config.xml b/shared/pc/overlay/frameworks/base/core/res/res/values/config.xml
index 313712f..ce0ba2b 100644
--- a/shared/pc/overlay/frameworks/base/core/res/res/values/config.xml
+++ b/shared/pc/overlay/frameworks/base/core/res/res/values/config.xml
@@ -20,8 +20,4 @@
<bool name="config_showNavigationBar" translatable="false">true</bool>
<!-- Maximum number of supported users -->
<integer name="config_multiuserMaximumUsers" translatable="false">4</integer>
- <!-- Restricting eth1 -->
- <string-array translatable="false" name="config_ethernet_interfaces">
- <item>eth1;11,12,14;;</item>
- </string-array>
</resources>
diff --git a/shared/permissions/Android.bp b/shared/permissions/Android.bp
new file mode 100644
index 0000000..1b1e7bb
--- /dev/null
+++ b/shared/permissions/Android.bp
@@ -0,0 +1,10 @@
+package {
+ default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+prebuilt_etc {
+ name: "cuttlefish_excluded_hardware.prebuilt.xml",
+ src: "cuttlefish_excluded_hardware.xml",
+ relative_install_path: "permissions",
+ soc_specific: true,
+}
diff --git a/shared/phone/device.mk b/shared/phone/device.mk
deleted file mode 100644
index faa2c7c..0000000
--- a/shared/phone/device.mk
+++ /dev/null
@@ -1,51 +0,0 @@
-#
-# Copyright (C) 2017 The Android Open Source Project
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-#
-
-PRODUCT_MANIFEST_FILES += device/google/cuttlefish/shared/config/product_manifest.xml
-SYSTEM_EXT_MANIFEST_FILES += device/google/cuttlefish/shared/config/system_ext_manifest.xml
-
-$(call inherit-product, $(SRC_TARGET_DIR)/product/aosp_base_telephony.mk)
-$(call inherit-product, frameworks/native/build/phone-xhdpi-2048-dalvik-heap.mk)
-$(call inherit-product, device/google/cuttlefish/shared/device.mk)
-
-PRODUCT_VENDOR_PROPERTIES += \
- keyguard.no_require_sim=true \
- ro.cdma.home.operator.alpha=Android \
- ro.cdma.home.operator.numeric=302780 \
- ro.telephony.default_network=9 \
-
-PRODUCT_PACKAGES += \
- MmsService \
- Phone \
- PhoneService \
- Telecom \
- TeleService \
- libcuttlefish-ril-2 \
- libcuttlefish-rild
-
-PRODUCT_COPY_FILES += \
- frameworks/native/data/etc/android.hardware.faketouch.xml:$(TARGET_COPY_OUT_VENDOR)/etc/permissions/android.hardware.faketouch.xml \
- frameworks/native/data/etc/android.hardware.telephony.gsm.xml:$(TARGET_COPY_OUT_VENDOR)/etc/permissions/android.hardware.telephony.gsm.xml
-
-DEVICE_PACKAGE_OVERLAYS += device/google/cuttlefish/shared/phone/overlay
-
-# These flags are important for the GSI, but break auto
-# These are used by aosp_cf_x86_go_phone targets
-PRODUCT_ENFORCE_RRO_TARGETS := framework-res
-
-# Storage: for factory reset protection feature
-PRODUCT_VENDOR_PROPERTIES += \
- ro.frp.pst=/dev/block/by-name/frp
diff --git a/shared/phone/device_vendor.mk b/shared/phone/device_vendor.mk
index 61f72c1..f373136 100644
--- a/shared/phone/device_vendor.mk
+++ b/shared/phone/device_vendor.mk
@@ -20,12 +20,16 @@
$(call inherit-product, $(SRC_TARGET_DIR)/product/handheld_vendor.mk)
$(call inherit-product, $(SRC_TARGET_DIR)/product/telephony_vendor.mk)
+ifneq ($(LOCAL_PREFER_VENDOR_APEX),true)
PRODUCT_COPY_FILES += \
frameworks/native/data/etc/handheld_core_hardware.xml:$(TARGET_COPY_OUT_VENDOR)/etc/permissions/handheld_core_hardware.xml
+endif
$(call inherit-product, frameworks/native/build/phone-xhdpi-2048-dalvik-heap.mk)
$(call inherit-product, device/google/cuttlefish/shared/device.mk)
+TARGET_PRODUCT_PROP := $(LOCAL_PATH)/product.prop
+
PRODUCT_VENDOR_PROPERTIES += \
keyguard.no_require_sim=true \
ro.cdma.home.operator.alpha=Android \
@@ -33,22 +37,31 @@
ro.com.android.dataroaming=true \
ro.telephony.default_network=9 \
-# TODO: not existing anymore?
-PRODUCT_PACKAGES += \
- Phone \
- PhoneService \
-
+TARGET_USES_CF_RILD ?= true
+ifeq ($(TARGET_USES_CF_RILD),true)
+ifeq ($(LOCAL_PREFER_VENDOR_APEX),true)
+PRODUCT_PACKAGES += com.google.cf.rild
+else
PRODUCT_PACKAGES += \
libcuttlefish-ril-2 \
libcuttlefish-rild
+endif
+endif
+ifneq ($(LOCAL_PREFER_VENDOR_APEX),true)
PRODUCT_COPY_FILES += \
frameworks/native/data/etc/android.hardware.biometrics.face.xml:$(TARGET_COPY_OUT_VENDOR)/etc/permissions/android.hardware.biometrics.face.xml \
frameworks/native/data/etc/android.hardware.faketouch.xml:$(TARGET_COPY_OUT_VENDOR)/etc/permissions/android.hardware.faketouch.xml \
frameworks/native/data/etc/android.hardware.fingerprint.xml:$(TARGET_COPY_OUT_VENDOR)/etc/permissions/android.hardware.fingerprint.xml \
frameworks/native/data/etc/android.hardware.telephony.gsm.xml:$(TARGET_COPY_OUT_VENDOR)/etc/permissions/android.hardware.telephony.gsm.xml \
frameworks/native/data/etc/android.hardware.telephony.ims.xml:$(TARGET_COPY_OUT_VENDOR)/etc/permissions/android.hardware.telephony.ims.xml
+endif
-DEVICE_PACKAGE_OVERLAYS += device/google/cuttlefish/shared/phone/overlay
+# Runtime Resource Overlays
+ifeq ($(LOCAL_PREFER_VENDOR_APEX),true)
+PRODUCT_PACKAGES += com.google.aosp_cf_phone.rros
+else
+PRODUCT_PACKAGES += cuttlefish_phone_overlay_frameworks_base_core
+endif
TARGET_BOARD_INFO_FILE ?= device/google/cuttlefish/shared/phone/android-info.txt
diff --git a/shared/phone/overlays/CuttlefishTetheringOverlay/Android.bp b/shared/phone/overlays/CuttlefishTetheringOverlay/Android.bp
new file mode 100644
index 0000000..a35bfd2
--- /dev/null
+++ b/shared/phone/overlays/CuttlefishTetheringOverlay/Android.bp
@@ -0,0 +1,10 @@
+package {
+ default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+runtime_resource_overlay {
+ name: "CuttlefishTetheringOverlay",
+ resource_dirs: ["res"],
+ vendor: true,
+ sdk_version: "current",
+}
diff --git a/shared/phone/overlays/CuttlefishTetheringOverlay/AndroidManifest.xml b/shared/phone/overlays/CuttlefishTetheringOverlay/AndroidManifest.xml
new file mode 100644
index 0000000..467d0ef
--- /dev/null
+++ b/shared/phone/overlays/CuttlefishTetheringOverlay/AndroidManifest.xml
@@ -0,0 +1,29 @@
+<!--
+ Copyright (C) 2021 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.networkstack.tethering.cuttlefishoverlay"
+ android:versionCode="1"
+ android:versionName="1.0" >
+
+ <application android:hasCode="false" />
+
+ <overlay
+ android:targetPackage="com.android.networkstack.tethering"
+ android:targetName="TetheringConfig"
+ android:priority="0"
+ android:isStatic="true"/>
+
+</manifest>
\ No newline at end of file
diff --git a/shared/phone/overlays/CuttlefishTetheringOverlay/res/values/config.xml b/shared/phone/overlays/CuttlefishTetheringOverlay/res/values/config.xml
new file mode 100644
index 0000000..00b6735
--- /dev/null
+++ b/shared/phone/overlays/CuttlefishTetheringOverlay/res/values/config.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+** Copyright 2022, The Android Open Source Project.
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+** http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+-->
+<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string-array name="config_tether_wifi_regexs">
+ <item>"wlan\\d"</item>
+ </string-array>
+ <string-array name="config_tether_wifi_p2p_regexs">
+ <item>"p2p-wlan\\d-.*"</item>
+ <item>"p2p-dev-wlan\\d-.*"</item>
+ <item>"p2p\\d"</item>
+ <item>"p2p-p2p\\d-.*"</item>
+ </string-array>
+</resources>
diff --git a/shared/phone/overlays/CuttlefishWifiOverlay/Android.bp b/shared/phone/overlays/CuttlefishWifiOverlay/Android.bp
new file mode 100644
index 0000000..fd2f7eb
--- /dev/null
+++ b/shared/phone/overlays/CuttlefishWifiOverlay/Android.bp
@@ -0,0 +1,10 @@
+package {
+ default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+runtime_resource_overlay {
+ name: "CuttlefishWifiOverlay",
+ resource_dirs: ["res"],
+ vendor: true,
+ sdk_version: "current",
+}
diff --git a/shared/phone/overlays/CuttlefishWifiOverlay/AndroidManifest.xml b/shared/phone/overlays/CuttlefishWifiOverlay/AndroidManifest.xml
new file mode 100644
index 0000000..bc29c5c
--- /dev/null
+++ b/shared/phone/overlays/CuttlefishWifiOverlay/AndroidManifest.xml
@@ -0,0 +1,29 @@
+<!--
+ Copyright (C) 2021 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.wifi.resources.cf"
+ android:versionCode="1"
+ android:versionName="1.0" >
+
+ <application android:hasCode="false" />
+
+ <overlay
+ android:targetPackage="com.android.wifi.resources"
+ android:targetName="WifiCustomization"
+ android:priority="0"
+ android:isStatic="true"/>
+
+</manifest>
\ No newline at end of file
diff --git a/shared/phone/overlays/CuttlefishWifiOverlay/res/values/config.xml b/shared/phone/overlays/CuttlefishWifiOverlay/res/values/config.xml
new file mode 100644
index 0000000..5586326
--- /dev/null
+++ b/shared/phone/overlays/CuttlefishWifiOverlay/res/values/config.xml
@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+** Copyright 2022, The Android Open Source Project.
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+** http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+-->
+<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <!-- True if the firmware supports connected MAC randomization -->
+ <!-- TODO(b/223101490) Disable temporarily for Wi-Fi connection issue -->
+ <bool name="config_wifi_connected_mac_randomization_supported">false</bool>
+
+ <!-- True if the firmware supports p2p MAC randomization -->
+ <bool name="config_wifi_p2p_mac_randomization_supported">true</bool>
+
+ <!-- True if the firmware supports ap MAC randomization -->
+ <bool name="config_wifi_ap_mac_randomization_supported">true</bool>
+
+</resources>
diff --git a/shared/phone/overlays/core/Android.bp b/shared/phone/overlays/core/Android.bp
new file mode 100644
index 0000000..23fddab
--- /dev/null
+++ b/shared/phone/overlays/core/Android.bp
@@ -0,0 +1,8 @@
+package {
+ default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+runtime_resource_overlay {
+ name: "cuttlefish_phone_overlay_frameworks_base_core",
+ soc_specific: true,
+}
diff --git a/shared/phone/overlays/core/AndroidManifest.xml b/shared/phone/overlays/core/AndroidManifest.xml
new file mode 100644
index 0000000..5ab8d7e
--- /dev/null
+++ b/shared/phone/overlays/core/AndroidManifest.xml
@@ -0,0 +1,11 @@
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="android.cuttlefish.phone.overlay">
+
+ <application android:hasCode="false" />
+
+ <overlay
+ android:targetPackage="android"
+ android:isStatic="true"
+ android:priority="2"
+ />
+</manifest>
diff --git a/shared/phone/overlay/frameworks/base/core/res/res/values/config.xml b/shared/phone/overlays/core/res/values/config.xml
similarity index 92%
rename from shared/phone/overlay/frameworks/base/core/res/res/values/config.xml
rename to shared/phone/overlays/core/res/values/config.xml
index 61feabf..1d7b019 100644
--- a/shared/phone/overlay/frameworks/base/core/res/res/values/config.xml
+++ b/shared/phone/overlays/core/res/values/config.xml
@@ -55,10 +55,6 @@
<string name="config_mms_user_agent_profile_url" translatable="false">http://gsm.lge.com/html/gsm/Nexus5-M3.xml</string>
<string name="config_wlan_data_service_package" translatable="false">com.android.ims</string>
<string name="config_wlan_network_service_package" translatable="false">com.android.ims</string>
- <!-- Restricting eth1 -->
- <string-array translatable="false" name="config_ethernet_interfaces">
- <item>eth1;11,12,14;;</item>
- </string-array>
<!-- List of biometric sensors on the device, in decreasing strength. Consumed by AuthService
when registering authenticators with BiometricService. Format must be ID:Modality:Strength,
@@ -68,4 +64,9 @@
<item>2:2:255</item> <!-- ID2:Fingerprint(HIDL):Weak -->
<item>3:8:255</item> <!-- ID3:Face(HIDL):Weak -->
</string-array>
+
+ <!-- Enable Night display, which requires HWC 2.0. -->
+ <bool name="config_nightDisplayAvailable">true</bool>
+ <!-- Let ColorFade use a color layer to avoid deadlocking in WM CTS. See b/233386717. -->
+ <bool name="config_animateScreenLights">true</bool>
</resources>
diff --git a/shared/phone/product.prop b/shared/phone/product.prop
new file mode 100644
index 0000000..2bce15e
--- /dev/null
+++ b/shared/phone/product.prop
@@ -0,0 +1,31 @@
+# Set the Bluetooth Class of Device
+# Service Field: 0x5A -> 90
+# Bit 17: Networking
+# Bit 19: Capturing
+# Bit 20: Object Transfer
+# Bit 22: Telephony
+# MAJOR_CLASS: 0x02 -> 2 (Phone)
+# MINOR_CLASS: 0x0C -> 12 (Smart Phone)
+bluetooth.device.class_of_device=90,2,12
+
+# Set supported Bluetooth profiles to enabled
+bluetooth.profile.asha.central.enabled=true
+bluetooth.profile.a2dp.source.enabled=true
+bluetooth.profile.avrcp.target.enabled=true
+bluetooth.profile.bap.broadcast.assist.enabled=true
+bluetooth.profile.bap.unicast.client.enabled=true
+bluetooth.profile.bas.client.enabled=true
+bluetooth.profile.csip.set_coordinator.enabled=true
+bluetooth.profile.gatt.enabled=true
+bluetooth.profile.hap.client.enabled=true
+bluetooth.profile.hfp.ag.enabled=true
+bluetooth.profile.hid.device.enabled=true
+bluetooth.profile.hid.host.enabled=true
+bluetooth.profile.map.server.enabled=true
+bluetooth.profile.mcp.server.enabled=true
+bluetooth.profile.opp.enabled=true
+bluetooth.profile.pan.nap.enabled=true
+bluetooth.profile.pan.panu.enabled=true
+bluetooth.profile.pbap.server.enabled=true
+bluetooth.profile.ccp.server.enabled=true
+bluetooth.profile.vcp.controller.enabled=true
diff --git a/shared/sepolicy/OWNERS b/shared/sepolicy/OWNERS
index 2975ddf..9b37b0e 100644
--- a/shared/sepolicy/OWNERS
+++ b/shared/sepolicy/OWNERS
@@ -1,12 +1,4 @@
[email protected]
+include platform/system/sepolicy:/OWNERS
+
[email protected]
[email protected]
[email protected]
[email protected]
[email protected]
[email protected]
[email protected]
[email protected]
[email protected]
[email protected]
[email protected]
[email protected]
diff --git a/shared/sepolicy/system_ext/private/logger_app.te b/shared/sepolicy/system_ext/private/logger_app.te
deleted file mode 100644
index 33d1934..0000000
--- a/shared/sepolicy/system_ext/private/logger_app.te
+++ /dev/null
@@ -1,17 +0,0 @@
-type logger_app, domain;
-
-# Taken from bonito-sepolicy:
-# https://cs.android.com/android/_/android/device/google/bonito-sepolicy/+/5396ef0aa04dc69ed04ecbc7f55eacf2a76b040b:vendor/qcom/common/logger_app.te;drc=dd2c2053296b0c00b5ef103adcabb8cd82eb0045
-userdebug_or_eng(`
- app_domain(logger_app)
- net_domain(logger_app)
-
- allow logger_app app_api_service:service_manager find;
- allow logger_app surfaceflinger_service:service_manager find;
- allow logger_app radio_vendor_data_file:file create_file_perms;
- allow logger_app radio_vendor_data_file:file rw_file_perms;
- allow logger_app radio_vendor_data_file:dir create_dir_perms;
- allow logger_app radio_vendor_data_file:dir rw_dir_perms;
-
- gpu_access(logger_app)
-')
diff --git a/shared/sepolicy/system_ext/private/sample_tuner_tis_app.te b/shared/sepolicy/system_ext/private/sample_tuner_tis_app.te
deleted file mode 100644
index 60f5e69..0000000
--- a/shared/sepolicy/system_ext/private/sample_tuner_tis_app.te
+++ /dev/null
@@ -1,7 +0,0 @@
-# Sample Tuner TIS app
-type sample_tuner_tis_app, domain;
-
-app_domain(sample_tuner_tis_app)
-
-allow sample_tuner_tis_app app_api_service:service_manager find;
-hal_client_domain(sample_tuner_tis_app, hal_tv_tuner);
diff --git a/shared/sepolicy/system_ext/private/seapp_contexts b/shared/sepolicy/system_ext/private/seapp_contexts
index 1cd8665..95768d0 100644
--- a/shared/sepolicy/system_ext/private/seapp_contexts
+++ b/shared/sepolicy/system_ext/private/seapp_contexts
@@ -1,9 +1,5 @@
# Ramdump app
user=_app seinfo=platform name=com.android.ramdump domain=ramdump_app type=app_data_file levelFrom=all
-user=_app seinfo=platform name=com.android.pixellogger domain=logger_app type=app_data_file levelFrom=all
# Connectivity monitor
user=_app isPrivApp=true seinfo=platform name=com.google.android.connectivitymonitor domain=con_monitor_app type=app_data_file levelFrom=all
-
-# Sample Tuner TIS
-user=system isPrivApp=true seinfo=platform name=com.android.tv.samples.sampletunertvinput domain=sample_tuner_tis_app type=app_data_file levelFrom=all
diff --git a/shared/sepolicy/vendor/adbd.te b/shared/sepolicy/vendor/adbd.te
index d932066..c933fe7 100644
--- a/shared/sepolicy/vendor/adbd.te
+++ b/shared/sepolicy/vendor/adbd.te
@@ -1,2 +1,4 @@
allow adbd self:{ socket vsock_socket } {create listen accept rw_socket_perms_no_ioctl};
allow adbd kernel:system module_request;
+
+gpu_access(adbd)
diff --git a/shared/sepolicy/vendor/bluetooth.te b/shared/sepolicy/vendor/bluetooth.te
new file mode 100644
index 0000000..aa2671a
--- /dev/null
+++ b/shared/sepolicy/vendor/bluetooth.te
@@ -0,0 +1 @@
+gpu_access(bluetooth)
diff --git a/shared/sepolicy/vendor/bootanim.te b/shared/sepolicy/vendor/bootanim.te
index 9ac7954..b183efa 100644
--- a/shared/sepolicy/vendor/bootanim.te
+++ b/shared/sepolicy/vendor/bootanim.te
@@ -1,6 +1,2 @@
-# TODO(b/65049764): Update this once the FD owner process is relabelled. This is probably one of the
-# processes whch is started before Android init.
-allow bootanim kernel:fd use;
-
allow bootanim self:process execmem;
gpu_access(bootanim)
diff --git a/shared/sepolicy/vendor/bug_map b/shared/sepolicy/vendor/bug_map
index 57cbf3c..fe3d21d 100644
--- a/shared/sepolicy/vendor/bug_map
+++ b/shared/sepolicy/vendor/bug_map
@@ -1,6 +1,8 @@
+init init capability b/199386018
+init logcat_exec file b/216584034
init system_lib_file dir b/133444385
init system_lib_file file b/133444385
+kernel kernel capability b/179966921
migrate_legacy_obb_data dalvikcache_data_file file b/152338071
-system_server system_server process b/65201432
gmscore_app hal_camera_prop file b/156287758
priv_app radio_vendor_data_file dir b/188833462
diff --git a/shared/sepolicy/vendor/cuttlefish_sensor_injection.te b/shared/sepolicy/vendor/cuttlefish_sensor_injection.te
index 9e7aca5..83f31f60 100644
--- a/shared/sepolicy/vendor/cuttlefish_sensor_injection.te
+++ b/shared/sepolicy/vendor/cuttlefish_sensor_injection.te
@@ -13,3 +13,4 @@
# Grant cuttlefish_sensor_injection access to the ISensors HAL.
hal_client_domain(cuttlefish_sensor_injection, hal_sensors)
+binder_use(cuttlefish_sensor_injection)
diff --git a/shared/sepolicy/vendor/dlkm_loader.te b/shared/sepolicy/vendor/dlkm_loader.te
new file mode 100644
index 0000000..c47e229
--- /dev/null
+++ b/shared/sepolicy/vendor/dlkm_loader.te
@@ -0,0 +1,17 @@
+type dlkm_loader, domain;
+type dlkm_loader_exec, exec_type, vendor_file_type, file_type;
+
+init_daemon_domain(dlkm_loader)
+
+# Allow insmod on vendor, system and system_dlkm partitions
+allow dlkm_loader self:capability sys_module;
+allow dlkm_loader system_file:system module_load;
+allow dlkm_loader system_dlkm_file:system module_load;
+allow dlkm_loader vendor_file:system module_load;
+
+# needed for libmodprobe to read kernel commandline
+allow dlkm_loader proc_cmdline:file r_file_perms;
+
+# dlkm_loader searches tracefs while looking for modules
+dontaudit dlkm_loader debugfs_bootreceiver_tracing:dir search;
+dontaudit dlkm_loader debugfs_mm_events_tracing:dir search;
diff --git a/shared/sepolicy/vendor/dumpstate.te b/shared/sepolicy/vendor/dumpstate.te
index 6233ca1..2e5483d 100644
--- a/shared/sepolicy/vendor/dumpstate.te
+++ b/shared/sepolicy/vendor/dumpstate.te
@@ -2,4 +2,6 @@
allow dumpstate system_data_file:dir w_dir_perms;
-allow dumpstate gpu_device:dir search;
\ No newline at end of file
+allow dumpstate gpu_device:dir search;
+
+allow dumpstate hal_sensors_default:binder call;
diff --git a/shared/sepolicy/vendor/file.te b/shared/sepolicy/vendor/file.te
index 53c4628..57a3eb0 100644
--- a/shared/sepolicy/vendor/file.te
+++ b/shared/sepolicy/vendor/file.te
@@ -1,7 +1,4 @@
# File types
type sensors_hal_socket, file_type;
-type tombstone_snapshot_file, file_type;
-type var_run_system_file, file_type;
-type sysfs_gpu, fs_type, sysfs_type;
type sysfs_iio_devices, fs_type, sysfs_type;
type mediadrm_vendor_data_file, file_type, data_file_type;
diff --git a/shared/sepolicy/vendor/file_contexts b/shared/sepolicy/vendor/file_contexts
index 49ef362..01dcc97 100644
--- a/shared/sepolicy/vendor/file_contexts
+++ b/shared/sepolicy/vendor/file_contexts
@@ -4,6 +4,7 @@
/dev/block/by-name/misc u:object_r:misc_block_device:s0
/dev/block/by-name/boot_[ab] u:object_r:boot_block_device:s0
+/dev/block/by-name/init_boot_[ab] u:object_r:boot_block_device:s0
/dev/block/by-name/vendor_boot_[ab] u:object_r:boot_block_device:s0
/dev/block/by-name/vbmeta_[ab] u:object_r:ab_block_device:s0
/dev/block/by-name/vbmeta_system_[ab] u:object_r:ab_block_device:s0
@@ -14,6 +15,7 @@
/dev/block/by-name/frp u:object_r:frp_block_device:s0
/dev/block/pmem0 u:object_r:rebootescrow_device:s0
+/dev/block/pmem1 u:object_r:hal_graphics_composer_pmem_device:s0
/dev/block/zram0 u:object_r:swap_block_device:s0
/dev/dri u:object_r:gpu_device:s0
/dev/dri/card0 u:object_r:graphics_device:s0
@@ -27,22 +29,20 @@
/dev/vhci u:object_r:bt_device:s0
+# gnss hal can also read/write from hvc6 and hvc7
+# hvc6 for gnss raw measurement
+# hvc7 for fixed location
+/dev/hvc6 u:object_r:gnss_device:s0
+/dev/hvc7 u:object_r:gnss_device:s0
+
# ARM serial console device
/dev/ttyAMA[0-9]* u:object_r:serial_device:s0
#############################
-# Root files
-/ts_snap\.txt u:object_r:tombstone_snapshot_file:s0
-
-#############################
# data files
/data/vendor/mediadrm(/.*)? u:object_r:mediadrm_vendor_data_file:s0
#############################
-# var files
-/var/run/system(/.*)? u:object_r:var_run_system_file:s0
-
-#############################
# sys files
# x86
/sys/devices/pci0000:00/0000:00:[0-9a-fA-F]{2}\.0/virtio[0-9]+/net(/.*)? u:object_r:sysfs_net:s0
@@ -57,6 +57,7 @@
# Vendor files
#
/vendor/bin/cuttlefish_sensor_injection u:object_r:cuttlefish_sensor_injection_exec:s0
+/vendor/bin/mac80211_create_radios u:object_r:mac80211_create_radios_exec:s0
/vendor/bin/socket_vsock_proxy u:object_r:socket_vsock_proxy_exec:s0
/vendor/bin/vsoc_input_service u:object_r:vsoc_input_service_exec:s0
/vendor/bin/rename_netiface u:object_r:rename_netiface_exec:s0
@@ -66,42 +67,54 @@
/vendor/bin/hw/android\.hardware\.camera\.provider@2\.7-service-google u:object_r:hal_camera_default_exec:s0
/vendor/bin/hw/android\.hardware\.camera\.provider@2\.7-service-google-lazy u:object_r:hal_camera_default_exec:s0
/vendor/bin/hw/android\.hardware\.power\.stats@1\.0-service\.mock u:object_r:hal_power_stats_default_exec:s0
+/vendor/bin/hw/android\.hardware.audio.service u:object_r:hal_audio_cuttlefish_exec:s0
/vendor/bin/hw/android\.hardware\.bluetooth@1\.1-service\.remote u:object_r:hal_bluetooth_remote_exec:s0
/vendor/bin/hw/android\.hardware\.bluetooth@1\.1-service\.sim u:object_r:hal_bluetooth_sim_exec:s0
/vendor/bin/hw/android\.hardware\.contexthub@1\.2-service\.mock u:object_r:hal_contexthub_default_exec:s0
/vendor/bin/hw/android\.hardware\.drm@[0-9]+\.[0-9]+-service\.clearkey u:object_r:hal_drm_clearkey_exec:s0
/vendor/bin/hw/android\.hardware\.drm@[0-9]+\.[0-9]+-service-lazy\.clearkey u:object_r:hal_drm_clearkey_exec:s0
/vendor/bin/hw/android\.hardware\.drm@[0-9]+\.[0-9]+-service\.widevine u:object_r:hal_drm_widevine_exec:s0
+/vendor/bin/hw/android\.hardware\.drm-service\.widevine u:object_r:hal_drm_widevine_exec:s0
/vendor/bin/hw/android\.hardware\.drm@[0-9]+\.[0-9]+-service-lazy\.widevine u:object_r:hal_drm_widevine_exec:s0
+/vendor/bin/hw/android\.hardware\.graphics\.allocator-V1-service\.minigbm u:object_r:hal_graphics_allocator_default_exec:s0
/vendor/bin/hw/android\.hardware\.graphics\.allocator@4\.0-service\.minigbm u:object_r:hal_graphics_allocator_default_exec:s0
+/vendor/bin/hw/android\.hardware\.graphics\.composer3-service\.ranchu u:object_r:hal_graphics_composer_default_exec:s0
/vendor/bin/hw/android\.hardware\.gatekeeper@1\.0-service\.software u:object_r:hal_gatekeeper_default_exec:s0
+/vendor/bin/hw/android\.hardware\.health-service\.cuttlefish u:object_r:hal_health_default_exec:s0
/vendor/bin/hw/android\.hardware\.health\.storage-service\.cuttlefish u:object_r:hal_health_storage_default_exec:s0
/vendor/bin/hw/android\.hardware\.lights-service\.example u:object_r:hal_light_default_exec:s0
/vendor/bin/hw/android\.hardware\.neuralnetworks@1\.3-service-sample-.* u:object_r:hal_neuralnetworks_sample_exec:s0
/vendor/bin/hw/android\.hardware\.neuralnetworks-shim-service-sample u:object_r:hal_neuralnetworks_sample_exec:s0
/vendor/bin/hw/android\.hardware\.neuralnetworks-service-sample-.* u:object_r:hal_neuralnetworks_sample_exec:s0
+/vendor/bin/hw/android\.hardware\.nfc-service\.cuttlefish u:object_r:hal_nfc_default_exec:s0
/vendor/bin/hw/android\.hardware\.vibrator@1\.x-service\.example u:object_r:hal_vibrator_default_exec:s0
+/vendor/bin/hw/android\.hardware\.net\.nlinterceptor-service\.default u:object_r:hal_nlinterceptor_default_exec:s0
/vendor/bin/setup_wifi u:object_r:setup_wifi_exec:s0
/vendor/bin/bt_vhci_forwarder u:object_r:bt_vhci_forwarder_exec:s0
-
+/vendor/bin/hw/android\.hardware\.sensors-service\.example u:object_r:hal_sensors_default_exec:s0
/vendor/bin/hw/android\.hardware\.sensors@2\.1-service\.mock u:object_r:hal_sensors_default_exec:s0
/vendor/bin/hw/android\.hardware\.input\.classifier@1\.0-service.default u:object_r:hal_input_classifier_default_exec:s0
+/vendor/bin/hw/android\.hardware\.input\.processor-service\.example u:object_r:hal_input_processor_default_exec:s0
/vendor/bin/hw/android\.hardware\.thermal@2\.0-service\.mock u:object_r:hal_thermal_default_exec:s0
+/vendor/bin/hw/android\.hardware\.identity-service\.remote u:object_r:hal_identity_remote_exec:s0
/vendor/bin/hw/android\.hardware\.security\.keymint-service\.remote u:object_r:hal_keymint_remote_exec:s0
/vendor/bin/hw/android\.hardware\.keymaster@4\.1-service.remote u:object_r:hal_keymaster_remote_exec:s0
/vendor/bin/hw/android\.hardware\.gatekeeper@1\.0-service.remote u:object_r:hal_gatekeeper_remote_exec:s0
+/vendor/bin/hw/android\.hardware\.confirmationui@1\.0-service.cuttlefish u:object_r:hal_confirmationui_cuttlefish_exec:s0
/vendor/bin/hw/android\.hardware\.oemlock-service.example u:object_r:hal_oemlock_default_exec:s0
/vendor/bin/hw/android\.hardware\.weaver-service.example u:object_r:hal_weaver_default_exec:s0
/vendor/bin/hw/android\.hardware\.authsecret@1\.0-service u:object_r:hal_authsecret_default_exec:s0
/vendor/bin/hw/android\.hardware\.authsecret-service.example u:object_r:hal_authsecret_default_exec:s0
/vendor/bin/hw/android\.hardware\.rebootescrow-service\.default u:object_r:hal_rebootescrow_default_exec:s0
-/vendor/bin/init\.insmod\.sh u:object_r:init_insmod_sh_exec:s0
+/vendor/bin/dlkm_loader u:object_r:dlkm_loader_exec:s0
+/vendor/bin/init\.wifi\.sh u:object_r:init_wifi_sh_exec:s0
/vendor/lib(64)?/libdrm.so u:object_r:same_process_hal_file:s0
/vendor/lib(64)?/libglapi.so u:object_r:same_process_hal_file:s0
/vendor/lib(64)?/dri/.* u:object_r:same_process_hal_file:s0
/vendor/lib(64)?/hw/android\.hardware\.graphics\.mapper@4\.0-impl\.minigbm\.so u:object_r:same_process_hal_file:s0
/vendor/lib(64)?/libminigbm_gralloc.so u:object_r:same_process_hal_file:s0
+/vendor/lib(64)?/libminigbm_gralloc4_utils.so u:object_r:same_process_hal_file:s0
/vendor/lib(64)?/hw/android\.hardware\.health@2\.0-impl-2\.1-cuttlefish\.so u:object_r:same_process_hal_file:s0
/vendor/lib(64)?/hw/vulkan.pastel.so u:object_r:same_process_hal_file:s0
/vendor/lib(64)?/libcuttlefish_fs.so u:object_r:same_process_hal_file:s0
diff --git a/shared/sepolicy/vendor/gceservice.te b/shared/sepolicy/vendor/gceservice.te
index b6f84be..57181ec 100644
--- a/shared/sepolicy/vendor/gceservice.te
+++ b/shared/sepolicy/vendor/gceservice.te
@@ -13,12 +13,6 @@
allow gceservice kmsg_device:chr_file w_file_perms;
allow gceservice kmsg_device:chr_file getattr;
-# Read tombstone snapshot file
-allow gceservice tombstone_snapshot_file:file r_file_perms;
-# List tombstone files
-allow gceservice tombstone_data_file:dir r_dir_perms;
-allow gceservice tombstone_data_file:file getattr;
-
# Communicate with GCE Metadata Proxy over Unix domain sockets
# The proxy process uses the default label ("kernel") because it is
# started before Android init and thus before SELinux rule are applied.
diff --git a/shared/sepolicy/vendor/genfs_contexts b/shared/sepolicy/vendor/genfs_contexts
index 3a18743..542db04 100644
--- a/shared/sepolicy/vendor/genfs_contexts
+++ b/shared/sepolicy/vendor/genfs_contexts
@@ -7,6 +7,7 @@
genfscon sysfs $1/0000:00:eval($2 + 1, 16, 2).0/virtio`'eval($3 + 1)`'/block u:object_r:sysfs_devices_block:s0 # vdb
genfscon sysfs $1/0000:00:eval($2 + 2, 16, 2).0/virtio`'eval($3 + 2)`'/block u:object_r:sysfs_devices_block:s0 # vdc
genfscon sysfs $1/0000:00:eval($2 + 3, 16, 2).0/virtio`'eval($3 + 3)`'/ndbus0 u:object_r:sysfs_devices_block:s0 # pmem0
+genfscon sysfs $1/0000:00:eval($2 + 4, 16, 2).0/virtio`'eval($3 + 3)`'/ndbus0 u:object_r:sysfs_devices_block:s0 # pmem1
dnl')dnl
dnl
dnl # $1 = pci prefix
@@ -29,42 +30,52 @@
dnl')dnl
dnl
# crosvm (x86)
-cf_pci_block_device(/devices/pci0000:00, 0x6, 5)
-cf_pci_gpu_device(/devices/pci0000:00, 0x11)
+cf_pci_block_device(/devices/pci0000:00, 0xb, 10)
+cf_pci_gpu_device(/devices/pci0000:00, 0x2)
## find /sys/devices/platform/* -type d -name 'rtc[0-9]' | sed 's,/rtc[0-9],,'
genfscon sysfs /devices/platform/rtc_cmos/rtc u:object_r:sysfs_rtc:s0
## find /sys/devices/platform/* -type d -name 'wakeup[0-9]'
genfscon sysfs /devices/LNXSYSTM:00/LNXSYBUS:00/PNP0A08:00/wakeup u:object_r:sysfs_wakeup:s0
+genfscon sysfs /devices/LNXSYSTM:00/LNXPWRBN:00/wakeup u:object_r:sysfs_wakeup:s0
+genfscon sysfs /devices/platform/rtc_cmos/rtc/rtc0/wakeup3 u:object_r:sysfs_wakeup:s0
cf_rtc_wakeup_alarmtimer(/devices/platform/rtc_cmos, 0, 1)
## currently disabled
#genfscon sysfs /devices/LNXSYSTM:00/GFSH0001:00/wakeup u:object_r:sysfs_wakeup:s0
+#genfscon sysfs /devices/platform/GFSH0001:00/power_supply u:object_r:sysfs_batteryinfo:s0
+#genfscon sysfs /devices/platform/GFSH0001:00/power_supply/ac/wakeup3 u:object_r:sysfs_wakeup:s0
+#genfscon sysfs /devices/platform/GFSH0001:00/power_supply/battery/wakeup4 u:object_r:sysfs_wakeup:s0
+genfscon sysfs /devices/virtual/mac80211_hwsim/hwsim0/net u:object_r:sysfs_net:s0
+genfscon sysfs /devices/virtual/mac80211_hwsim/hwsim1/net u:object_r:sysfs_net:s0
# crosvm (arm64)
-cf_pci_block_device(/devices/platform/10000.pci, 0x6, 4)
-cf_pci_gpu_device(/devices/platform/10000.pci/pci0000:00, 0x11)
+cf_pci_block_device(/devices/platform/10000.pci/pci0000:00, 0xb, 10)
+cf_pci_gpu_device(/devices/platform/10000.pci/pci0000:00, 0x2)
## find /sys/devices/platform/* -type d -name 'rtc[0-9]' | sed 's,/rtc[0-9],,'
genfscon sysfs /devices/platform/2000.rtc/rtc u:object_r:sysfs_rtc:s0
## find /sys/devices/platform/* -type d -name 'wakeup[0-9]'
## arm64 2000.rtc on crosvm does not currently expose a wakeup node
# qemu (x86)
-cf_pci_block_device(/devices/pci0000:00, 0x7, 5)
+cf_pci_block_device(/devices/pci0000:00, 0xb, 9)
+#cf_pci_gpu_device(/devices/pci0000:00, 0x2) - duplicated with crosvm(x86)
## find /sys/devices/platform/* -type d -name 'rtc[0-9]' | sed 's,/rtc[0-9],,'
genfscon sysfs /devices/pnp0/00:04/rtc u:object_r:sysfs_rtc:s0
## find /sys/devices/platform/* -type d -name 'wakeup[0-9][0-9]'
cf_rtc_wakeup_alarmtimer(/devices/pnp0/00:04, 0, 19)
# qemu (arm64)
-cf_pci_block_device(/devices/platform/4010000000.pcie/pci0000:00, 0x6, 4)
-cf_pci_gpu_device(/devices/platform/4010000000.pcie/pci0000:00, 0x10)
+cf_pci_block_device(/devices/platform/4010000000.pcie/pci0000:00, 0xa, 9)
+cf_pci_gpu_device(/devices/platform/4010000000.pcie/pci0000:00, 0x2)
## find /sys/devices/platform/* -type d -name 'rtc[0-9]' | sed 's,/rtc[0-9],,'
genfscon sysfs /devices/platform/9010000.pl031/rtc u:object_r:sysfs_rtc:s0
## find /sys/devices/platform/* -type d -name 'wakeup[0-9]'
cf_rtc_wakeup_alarmtimer(/devices/platform/9010000.pl031, 0, 0)
# qemu (arm)
-cf_pci_block_device(/devices/platform/3f000000.pcie/pci0000:00, 0x6, 4)
-cf_pci_gpu_device(/devices/platform/3f000000.pcie/pci0000:00, 0xf)
+cf_pci_block_device(/devices/platform/3f000000.pcie/pci0000:00, 0xa, 9)
+cf_pci_gpu_device(/devices/platform/3f000000.pcie/pci0000:00, 0x2)
+genfscon sysfs /devices/platform/rtc-test.1/wakeup/wakeup2 u:object_r:sysfs_wakeup:s0
+genfscon sysfs /devices/platform/rtc-test.2/wakeup/wakeup3 u:object_r:sysfs_wakeup:s0
# common on all platforms / vm managers
genfscon sysfs /devices/platform/rtc-test.0/rtc u:object_r:sysfs_rtc:s0
diff --git a/shared/sepolicy/vendor/hal_audio_cuttlefish.te b/shared/sepolicy/vendor/hal_audio_cuttlefish.te
new file mode 100644
index 0000000..0bdd256
--- /dev/null
+++ b/shared/sepolicy/vendor/hal_audio_cuttlefish.te
@@ -0,0 +1,9 @@
+type hal_audio_cuttlefish, domain;
+type hal_audio_cuttlefish_exec, exec_type, vendor_file_type, file_type;
+
+hal_server_domain(hal_audio_cuttlefish, hal_audio)
+
+init_daemon_domain(hal_audio_cuttlefish)
+
+binder_use(hal_audio_cuttlefish)
+allow hal_audio_cuttlefish audioserver:fifo_file write;
diff --git a/shared/sepolicy/vendor/hal_camera_default.te b/shared/sepolicy/vendor/hal_camera_default.te
index e4dac76..e4a1156 100644
--- a/shared/sepolicy/vendor/hal_camera_default.te
+++ b/shared/sepolicy/vendor/hal_camera_default.te
@@ -13,3 +13,8 @@
# Vsocket camera
allow hal_camera_default self:vsock_socket { accept bind create getopt listen read write };
+
+# The camera HAL can respond to APEX updates (see ApexUpdateListener), but this
+# is not used by the emulated camera HAL APEX. Ignore these denials.
+dontaudit hal_camera_default property_socket:sock_file { write };
+dontaudit hal_camera_default apex_info_file:file { read };
diff --git a/shared/sepolicy/vendor/hal_confirmationui_cuttlefish.te b/shared/sepolicy/vendor/hal_confirmationui_cuttlefish.te
new file mode 100644
index 0000000..13cd1a9
--- /dev/null
+++ b/shared/sepolicy/vendor/hal_confirmationui_cuttlefish.te
@@ -0,0 +1,14 @@
+type hal_confirmationui_cuttlefish, domain;
+hal_server_domain(hal_confirmationui_cuttlefish, hal_confirmationui)
+
+type hal_confirmationui_cuttlefish_exec, exec_type, vendor_file_type, file_type;
+init_daemon_domain(hal_confirmationui_cuttlefish)
+
+vendor_internal_prop(vendor_vsock_confirmationui_port_prop)
+get_prop(hal_confirmationui_cuttlefish, vendor_vsock_confirmationui_port_prop)
+
+allow hal_confirmationui_cuttlefish self:{ vsock_socket } { create getopt read write getattr connect shutdown };
+
+# Write to kernel log (/dev/kmsg)
+allow hal_confirmationui_cuttlefish kmsg_device:chr_file w_file_perms;
+allow hal_confirmationui_cuttlefish kmsg_device:chr_file getattr;
diff --git a/shared/sepolicy/vendor/hal_graphics_composer.te b/shared/sepolicy/vendor/hal_graphics_composer.te
index 4929038..d08af30 100644
--- a/shared/sepolicy/vendor/hal_graphics_composer.te
+++ b/shared/sepolicy/vendor/hal_graphics_composer.te
@@ -1,8 +1,11 @@
-vendor_restricted_prop(vendor_vsock_frames_port_prop)
-
allow hal_graphics_composer_server hal_graphics_allocator_default_tmpfs:file read;
allow hal_graphics_composer_server self:{ socket vsock_socket } create_socket_perms_no_ioctl;
gpu_access(hal_graphics_composer_server)
-get_prop(hal_graphics_composer_server, vendor_vsock_frames_port_prop)
get_prop(hal_graphics_composer_server, vendor_cuttlefish_config_server_port_prop)
+get_prop(hal_graphics_composer_server, vendor_hwcomposer_prop)
+
+# Persistent memory for some hwcomposer configuration.
+type hal_graphics_composer_pmem_device, dev_type;
+allow hal_graphics_composer_server hal_graphics_composer_pmem_device:blk_file rw_file_perms;
+allow hal_graphics_composer_server block_device:dir search;
diff --git a/shared/sepolicy/vendor/hal_identity_remote.te b/shared/sepolicy/vendor/hal_identity_remote.te
new file mode 100644
index 0000000..3f89226
--- /dev/null
+++ b/shared/sepolicy/vendor/hal_identity_remote.te
@@ -0,0 +1,5 @@
+type hal_identity_remote, domain;
+hal_server_domain(hal_identity_remote, hal_identity)
+
+type hal_identity_remote_exec, exec_type, vendor_file_type, file_type;
+init_daemon_domain(hal_identity_remote)
diff --git a/shared/sepolicy/vendor/hal_keymint_remote.te b/shared/sepolicy/vendor/hal_keymint_remote.te
index 27f8291..7d5f6d5 100644
--- a/shared/sepolicy/vendor/hal_keymint_remote.te
+++ b/shared/sepolicy/vendor/hal_keymint_remote.te
@@ -10,3 +10,6 @@
# Write to kernel log (/dev/kmsg)
allow hal_keymint_remote kmsg_device:chr_file w_file_perms;
allow hal_keymint_remote kmsg_device:chr_file getattr;
+
+get_prop(hal_keymint_remote, vendor_security_patch_level_prop)
+get_prop(hal_keymint_remote, vendor_boot_security_patch_level_prop)
diff --git a/shared/sepolicy/vendor/hal_nlinterceptor_default.te b/shared/sepolicy/vendor/hal_nlinterceptor_default.te
new file mode 100644
index 0000000..2419db8
--- /dev/null
+++ b/shared/sepolicy/vendor/hal_nlinterceptor_default.te
@@ -0,0 +1,5 @@
+type hal_nlinterceptor_default, domain;
+hal_server_domain(hal_nlinterceptor_default, hal_nlinterceptor)
+
+type hal_nlinterceptor_default_exec, exec_type, vendor_file_type, file_type;
+init_daemon_domain(hal_nlinterceptor_default)
diff --git a/shared/sepolicy/vendor/hal_sensors.te b/shared/sepolicy/vendor/hal_sensors.te
index 27fc9c8..827060f 100644
--- a/shared/sepolicy/vendor/hal_sensors.te
+++ b/shared/sepolicy/vendor/hal_sensors.te
@@ -1 +1,3 @@
-allow hal_sensors_server sensors_hal_socket:sock_file { create setattr };
\ No newline at end of file
+allow hal_sensors_server sensors_hal_socket:sock_file { create setattr };
+
+allow hal_sensors_default system_server:binder call;
diff --git a/shared/sepolicy/vendor/hal_wifi_default.te b/shared/sepolicy/vendor/hal_wifi_default.te
new file mode 100644
index 0000000..e14c6cb
--- /dev/null
+++ b/shared/sepolicy/vendor/hal_wifi_default.te
@@ -0,0 +1,4 @@
+allow hal_wifi_default hal_wifi_default:netlink_route_socket {
+ create bind write read nlmsg_read nlmsg_readpriv };
+allow hal_wifi_default self:capability { sys_module };
+set_prop(hal_wifi_default, vendor_wlan_versions_prop)
diff --git a/shared/sepolicy/vendor/init.te b/shared/sepolicy/vendor/init.te
index 7f362ef..a19eb13 100644
--- a/shared/sepolicy/vendor/init.te
+++ b/shared/sepolicy/vendor/init.te
@@ -12,8 +12,12 @@
allow init binfmt_miscfs:file w_file_perms;
allow init proc:dir mounton;
-# init relabel vbmeta* symlinks
+# init relabel vbmeta* and boot* symlinks under /dev/block/by-name/.
allow init ab_block_device:lnk_file relabelto;
+allow init boot_block_device:lnk_file relabelto;
+
+# init needs to tune block device
+allow init sysfs_devices_block:file w_file_perms;
# /mnt/sdcard -> /storage/self/primary symlink is deprecated. Ignore attempts to
# create it. This denial is fixed in core policy in Android R aosp/943799.
diff --git a/shared/sepolicy/vendor/init_insmod_sh.te b/shared/sepolicy/vendor/init_insmod_sh.te
deleted file mode 100644
index 5400a37..0000000
--- a/shared/sepolicy/vendor/init_insmod_sh.te
+++ /dev/null
@@ -1,11 +0,0 @@
-type init_insmod_sh, domain;
-type init_insmod_sh_exec, exec_type, vendor_file_type, file_type;
-
-init_daemon_domain(init_insmod_sh)
-
-allow init_insmod_sh vendor_shell_exec:file rx_file_perms;
-allow init_insmod_sh vendor_toolbox_exec:file rx_file_perms;
-
-# Allow insmod
-allow init_insmod_sh self:capability sys_module;
-allow init_insmod_sh vendor_file:system module_load;
diff --git a/shared/sepolicy/vendor/init_wifi_sh.te b/shared/sepolicy/vendor/init_wifi_sh.te
new file mode 100644
index 0000000..331a745
--- /dev/null
+++ b/shared/sepolicy/vendor/init_wifi_sh.te
@@ -0,0 +1,14 @@
+# cuttlefish-setup service: runs init.cuttlefish.sh script
+type init_wifi_sh, domain;
+type init_wifi_sh_exec, vendor_file_type, exec_type, file_type;
+
+init_daemon_domain(init_wifi_sh)
+
+allow init_wifi_sh self:capability { fowner chown net_admin net_raw };
+allow init_wifi_sh vendor_toolbox_exec:file execute_no_trans;
+allow init_wifi_sh mac80211_create_radios_exec:file execute_no_trans;
+
+vendor_internal_prop(vendor_wifi_mac_prefix);
+get_prop(init_wifi_sh, vendor_wifi_mac_prefix);
+
+allow init_wifi_sh self:netlink_generic_socket create_socket_perms_no_ioctl;
diff --git a/shared/sepolicy/vendor/libcuttlefish_rild.te b/shared/sepolicy/vendor/libcuttlefish_rild.te
index 8f3bbe7..28412c7 100644
--- a/shared/sepolicy/vendor/libcuttlefish_rild.te
+++ b/shared/sepolicy/vendor/libcuttlefish_rild.te
@@ -11,4 +11,4 @@
get_prop(libcuttlefish_rild, vendor_cuttlefish_config_server_port_prop)
get_prop(libcuttlefish_rild, vendor_modem_simulator_ports_prop)
-allow libcuttlefish_rild self:{ socket vsock_socket } create_socket_perms_no_ioctl;
+allow libcuttlefish_rild self:{ socket vsock_socket } { create_socket_perms_no_ioctl getattr };
diff --git a/shared/sepolicy/vendor/mac80211_create_radios.te b/shared/sepolicy/vendor/mac80211_create_radios.te
new file mode 100644
index 0000000..f7e6b7f
--- /dev/null
+++ b/shared/sepolicy/vendor/mac80211_create_radios.te
@@ -0,0 +1,2 @@
+type mac80211_create_radios, domain;
+type mac80211_create_radios_exec, exec_type, vendor_file_type, file_type;
diff --git a/shared/sepolicy/system_ext/private/mediatranscoding.te b/shared/sepolicy/vendor/mediatranscoding.te
similarity index 100%
rename from shared/sepolicy/system_ext/private/mediatranscoding.te
rename to shared/sepolicy/vendor/mediatranscoding.te
diff --git a/shared/sepolicy/vendor/platform_app.te b/shared/sepolicy/vendor/platform_app.te
index ba23d18..bb4160d 100644
--- a/shared/sepolicy/vendor/platform_app.te
+++ b/shared/sepolicy/vendor/platform_app.te
@@ -1,3 +1,4 @@
gpu_access(platform_app)
-allow platform_app broadcastradio_service:service_manager find;
\ No newline at end of file
+allow platform_app broadcastradio_service:service_manager find;
+allow platform_app hal_wlc_hwservice:hwservice_manager find;
\ No newline at end of file
diff --git a/shared/sepolicy/vendor/property.te b/shared/sepolicy/vendor/property.te
index a727365..91b30fc 100644
--- a/shared/sepolicy/vendor/property.te
+++ b/shared/sepolicy/vendor/property.te
@@ -1,2 +1,5 @@
vendor_restricted_prop(vendor_cuttlefish_config_server_port_prop)
vendor_internal_prop(vendor_modem_simulator_ports_prop)
+vendor_internal_prop(vendor_boot_security_patch_level_prop)
+vendor_internal_prop(vendor_hwcomposer_prop)
+vendor_restricted_prop(vendor_wlan_versions_prop)
diff --git a/shared/sepolicy/vendor/property_contexts b/shared/sepolicy/vendor/property_contexts
index ebbe271..9b98ed1 100644
--- a/shared/sepolicy/vendor/property_contexts
+++ b/shared/sepolicy/vendor/property_contexts
@@ -5,10 +5,16 @@
ro.boot.hardware.hwcomposer u:object_r:vendor_graphics_config_prop:s0 exact string
ro.boot.hardware.vulkan u:object_r:vendor_graphics_config_prop:s0 exact string
ro.boot.lcd_density u:object_r:vendor_graphics_config_prop:s0 exact int
-ro.boot.vsock_frames_port u:object_r:vendor_vsock_frames_port_prop:s0
ro.boot.vsock_keyboard_port u:object_r:vendor_vsock_keyboard_port:s0
+ro.boot.vsock_confirmationui_port u:object_r:vendor_vsock_confirmationui_port_prop:s0
ro.boot.modem_simulator_ports u:object_r:vendor_modem_simulator_ports_prop:s0
ro.boot.vsock_touch_port u:object_r:vendor_vsock_touch_port:s0
-ro.boot.wifi_mac_address u:object_r:vendor_wifi_mac_address:s0
+ro.boot.wifi_mac_prefix u:object_r:vendor_wifi_mac_prefix:s0 exact string
+ro.vendor.wifi_impl u:object_r:vendor_wifi_impl:s0 exact string
+ro.vendor.boot_security_patch u:object_r:vendor_boot_security_patch_level_prop:s0
vendor.bt.rootcanal_mac_address u:object_r:vendor_bt_rootcanal_prop:s0
vendor.bt.rootcanal_test_console u:object_r:vendor_bt_rootcanal_prop:s0
+ro.vendor.hwcomposer.mode u:object_r:vendor_hwcomposer_prop:s0 exact string
+ro.vendor.hwcomposer.pmem u:object_r:vendor_hwcomposer_prop:s0 exact string
+vendor.wlan.firmware.version u:object_r:vendor_wlan_versions_prop:s0 exact string
+vendor.wlan.driver.version u:object_r:vendor_wlan_versions_prop:s0 exact string
diff --git a/shared/sepolicy/vendor/rename_netiface.te b/shared/sepolicy/vendor/rename_netiface.te
index 1ec0f06..f648a7e 100644
--- a/shared/sepolicy/vendor/rename_netiface.te
+++ b/shared/sepolicy/vendor/rename_netiface.te
@@ -5,6 +5,6 @@
allow rename_netiface self:capability { net_admin net_raw sys_module };
allow rename_netiface self:udp_socket { create ioctl };
-allow rename_netiface self:netlink_route_socket { bind create nlmsg_write read write };
+allow rename_netiface self:netlink_route_socket { bind create nlmsg_write read write getattr };
allow rename_netiface kernel:system module_request;
diff --git a/shared/sepolicy/vendor/service_contexts b/shared/sepolicy/vendor/service_contexts
index d20d026..c41503e 100644
--- a/shared/sepolicy/vendor/service_contexts
+++ b/shared/sepolicy/vendor/service_contexts
@@ -1,3 +1,4 @@
+android.hardware.drm.IDrmFactory/widevine u:object_r:hal_drm_service:s0
android.hardware.neuralnetworks.IDevice/nnapi-sample_all u:object_r:hal_neuralnetworks_service:s0
android.hardware.neuralnetworks.IDevice/nnapi-sample_float_fast u:object_r:hal_neuralnetworks_service:s0
android.hardware.neuralnetworks.IDevice/nnapi-sample_float_slow u:object_r:hal_neuralnetworks_service:s0
diff --git a/shared/sepolicy/vendor/setup_wifi.te b/shared/sepolicy/vendor/setup_wifi.te
index 23a34eb..6a8d054 100644
--- a/shared/sepolicy/vendor/setup_wifi.te
+++ b/shared/sepolicy/vendor/setup_wifi.te
@@ -5,10 +5,8 @@
allow setup_wifi self:capability { net_admin net_raw sys_module };
allow setup_wifi self:udp_socket { create ioctl };
-allow setup_wifi self:netlink_route_socket { bind create nlmsg_write read write };
+allow setup_wifi self:netlink_route_socket { bind create nlmsg_write read write getattr };
allow setup_wifi kernel:system module_request;
-vendor_internal_prop(vendor_wifi_mac_address)
-
-get_prop(setup_wifi, vendor_wifi_mac_address)
+get_prop(setup_wifi, vendor_wifi_mac_prefix)
diff --git a/shared/sepolicy/vendor/socket_vsock_proxy.te b/shared/sepolicy/vendor/socket_vsock_proxy.te
index d4e2b1a..6f72963 100644
--- a/shared/sepolicy/vendor/socket_vsock_proxy.te
+++ b/shared/sepolicy/vendor/socket_vsock_proxy.te
@@ -4,7 +4,7 @@
init_daemon_domain(socket_vsock_proxy)
allow socket_vsock_proxy self:global_capability_class_set { net_admin net_raw };
-allow socket_vsock_proxy self:{ socket vsock_socket } { create getopt read write listen accept bind shutdown };
+allow socket_vsock_proxy self:{ socket vsock_socket } { create getopt read write getattr listen accept bind shutdown };
# TODO: socket returned by accept() has unlabeled context on it. Give it a
# specific label.
diff --git a/shared/sepolicy/vendor/system_server.te b/shared/sepolicy/vendor/system_server.te
index 372ca50..171ea52 100644
--- a/shared/sepolicy/vendor/system_server.te
+++ b/shared/sepolicy/vendor/system_server.te
@@ -1,11 +1,10 @@
-# TODO(b/65201432): Switch into enforcing mode once execmem issue due to OpenGL is resolved. Also
-# remove the corresponding dontaudit.
-# The current (at the time of writing) implementation of OpenGL needs to create executable memory.
-# Unfortunately, we cannot grant execmem power using an allow rule because global policy
-# (system/sepolicy) contains a corresponding neverallow which would cause build-time errors if the
-# allow execmem rule were added here.
-permissive system_server;
gpu_access(system_server)
# Cuttlefish is still using the legacy wifi HAL (pre-HIDL)
get_prop(system_server, wifi_hal_prop)
+
+# TODO(b/65201432): Swiftshader needs to create executable memory.
+allow system_server self:process execmem;
+
+# For com.android.tethering.inprocess
+dontaudit system_server { fs_bpf fs_bpf_tethering }:dir search;
diff --git a/shared/sepolicy/vendor/vendor_init.te b/shared/sepolicy/vendor/vendor_init.te
index 37e76e1..6a01641 100644
--- a/shared/sepolicy/vendor/vendor_init.te
+++ b/shared/sepolicy/vendor/vendor_init.te
@@ -5,5 +5,11 @@
}:chr_file { getattr };
set_prop(vendor_init, vendor_bt_rootcanal_prop)
+set_prop(vendor_init, vendor_hwcomposer_prop)
get_prop(vendor_init, vendor_graphics_config_prop)
+
+vendor_internal_prop(vendor_wifi_impl)
+set_prop(vendor_init, vendor_wifi_impl)
+
+set_prop(vendor_init, vendor_boot_security_patch_level_prop)
diff --git a/shared/slim/Android.bp b/shared/slim/Android.bp
new file mode 100644
index 0000000..2d5cac7
--- /dev/null
+++ b/shared/slim/Android.bp
@@ -0,0 +1,10 @@
+package {
+ default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+prebuilt_etc {
+ name: "slim_excluded_hardware.prebuilt.xml",
+ src: "slim_excluded_hardware.xml",
+ relative_install_path: "permissions",
+ soc_specific: true,
+}
diff --git a/shared/slim/android-info.txt b/shared/slim/android-info.txt
new file mode 100644
index 0000000..b0bf39b
--- /dev/null
+++ b/shared/slim/android-info.txt
@@ -0,0 +1 @@
+config=slim
diff --git a/shared/slim/device_vendor.mk b/shared/slim/device_vendor.mk
new file mode 100644
index 0000000..cc46e5c
--- /dev/null
+++ b/shared/slim/device_vendor.mk
@@ -0,0 +1,73 @@
+#
+# Copyright (C) 2022 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+PRODUCT_MANIFEST_FILES += device/google/cuttlefish/shared/config/product_manifest.xml
+SYSTEM_EXT_MANIFEST_FILES += device/google/cuttlefish/shared/config/system_ext_manifest.xml
+
+$(call inherit-product, $(SRC_TARGET_DIR)/product/handheld_vendor.mk)
+$(call inherit-product, $(SRC_TARGET_DIR)/product/telephony_vendor.mk)
+
+ifneq ($(LOCAL_PREFER_VENDOR_APEX),true)
+PRODUCT_COPY_FILES += \
+ frameworks/native/data/etc/handheld_core_hardware.xml:$(TARGET_COPY_OUT_VENDOR)/etc/permissions/handheld_core_hardware.xml
+PRODUCT_PACKAGES += slim_excluded_hardware.prebuilt.xml
+endif
+
+$(call inherit-product, frameworks/native/build/phone-xhdpi-2048-dalvik-heap.mk)
+$(call inherit-product, device/google/cuttlefish/shared/device.mk)
+
+PRODUCT_VENDOR_PROPERTIES += \
+ keyguard.no_require_sim=true \
+ ro.cdma.home.operator.alpha=Android \
+ ro.cdma.home.operator.numeric=302780 \
+ ro.com.android.dataroaming=true \
+ ro.telephony.default_network=9 \
+
+TARGET_USES_CF_RILD ?= true
+ifeq ($(TARGET_USES_CF_RILD),true)
+ifeq ($(LOCAL_PREFER_VENDOR_APEX),true)
+PRODUCT_PACKAGES += com.google.cf.rild
+else
+PRODUCT_PACKAGES += \
+ libcuttlefish-ril-2 \
+ libcuttlefish-rild
+endif
+endif
+
+PRODUCT_VENDOR_PROPERTIES += \
+ debug.hwui.drawing_enabled=0 \
+
+ifneq ($(LOCAL_PREFER_VENDOR_APEX),true)
+PRODUCT_COPY_FILES += \
+ frameworks/native/data/etc/android.hardware.biometrics.face.xml:$(TARGET_COPY_OUT_VENDOR)/etc/permissions/android.hardware.biometrics.face.xml \
+ frameworks/native/data/etc/android.hardware.faketouch.xml:$(TARGET_COPY_OUT_VENDOR)/etc/permissions/android.hardware.faketouch.xml \
+ frameworks/native/data/etc/android.hardware.fingerprint.xml:$(TARGET_COPY_OUT_VENDOR)/etc/permissions/android.hardware.fingerprint.xml \
+ frameworks/native/data/etc/android.hardware.telephony.gsm.xml:$(TARGET_COPY_OUT_VENDOR)/etc/permissions/android.hardware.telephony.gsm.xml \
+ frameworks/native/data/etc/android.hardware.telephony.ims.xml:$(TARGET_COPY_OUT_VENDOR)/etc/permissions/android.hardware.telephony.ims.xml
+endif
+
+# Runtime Resource Overlays
+ifeq ($(LOCAL_PREFER_VENDOR_APEX),true)
+PRODUCT_PACKAGES += \
+ com.google.aosp_cf_phone.rros \
+ com.google.aosp_cf_slim.rros
+else
+PRODUCT_PACKAGES += \
+ cuttlefish_phone_overlay_frameworks_base_core \
+ slim_overlay_frameworks_base_core
+endif
+
+TARGET_BOARD_INFO_FILE ?= device/google/cuttlefish/shared/slim/android-info.txt
diff --git a/shared/slim/slim_excluded_hardware.xml b/shared/slim/slim_excluded_hardware.xml
new file mode 100644
index 0000000..a5263e2
--- /dev/null
+++ b/shared/slim/slim_excluded_hardware.xml
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright 2022 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<permissions>
+ <unavailable-feature name="android.software.secure_lock_screen" />
+ <unavailable-feature name="android.software.picture_in_picture" />
+</permissions>
diff --git a/shared/tv/OWNERS b/shared/tv/OWNERS
new file mode 100644
index 0000000..4df9f27
--- /dev/null
+++ b/shared/tv/OWNERS
@@ -0,0 +1,2 @@
+# Bug component: 760438
+include device/google/atv:/OWNERS
diff --git a/shared/tv/device.mk b/shared/tv/device_vendor.mk
similarity index 70%
rename from shared/tv/device.mk
rename to shared/tv/device_vendor.mk
index 9b78ee1..5b91b64 100644
--- a/shared/tv/device.mk
+++ b/shared/tv/device_vendor.mk
@@ -14,22 +14,25 @@
# limitations under the License.
#
-DEVICE_MANIFEST_FILE += device/google/cuttlefish/shared/tv/manifest.xml
+PRODUCT_MANIFEST_FILES += device/google/cuttlefish/shared/config/product_manifest.xml
+SYSTEM_EXT_MANIFEST_FILES += device/google/cuttlefish/shared/config/system_ext_manifest.xml
-$(call inherit-product, $(SRC_TARGET_DIR)/product/core_minimal.mk)
+$(call inherit-product, device/google/atv/products/atv_vendor.mk)
$(call inherit-product, device/google/cuttlefish/shared/device.mk)
+$(call inherit-product, frameworks/native/build/phone-xhdpi-2048-dalvik-heap.mk)
# Extend cuttlefish common sepolicy with tv-specific functionality
BOARD_SEPOLICY_DIRS += device/google/cuttlefish/shared/tv/sepolicy/vendor
PRODUCT_COPY_FILES += \
- device/google/atv/permissions/tv_core_hardware.xml:$(TARGET_COPY_OUT_VENDOR)/etc/permissions/tv_core_hardware.xml \
frameworks/native/data/etc/android.hardware.bluetooth.xml:$(TARGET_COPY_OUT_VENDOR)/etc/permissions/android.hardware.bluetooth.xml \
frameworks/native/data/etc/android.hardware.hdmi.cec.xml:$(TARGET_COPY_OUT_VENDOR)/etc/permissions/android.hardware.hdmi.cec.xml \
frameworks/native/data/etc/android.hardware.sensor.accelerometer.xml:$(TARGET_COPY_OUT_VENDOR)/etc/permissions/android.hardware.sensor.accelerometer.xml \
frameworks/native/data/etc/android.hardware.sensor.compass.xml:$(TARGET_COPY_OUT_VENDOR)/etc/permissions/android.hardware.sensor.compass.xml \
+ frameworks/native/data/etc/android.hardware.tv.tuner.xml:$(TARGET_COPY_OUT_VENDOR)/etc/permissions/android.hardware.tv.tuner.xml \
hardware/interfaces/tv/tuner/config/sample_tuner_vts_config_1_0.xml:$(TARGET_COPY_OUT_VENDOR)/etc/tuner_vts_config_1_0.xml \
hardware/interfaces/tv/tuner/config/sample_tuner_vts_config_1_1.xml:$(TARGET_COPY_OUT_VENDOR)/etc/tuner_vts_config_1_1.xml \
+ hardware/interfaces/tv/tuner/config/sample_tuner_vts_config_aidl_V1.xml:$(TARGET_COPY_OUT_VENDOR)/etc/tuner_vts_config_aidl_V1.xml
# HDMI CEC HAL
PRODUCT_PACKAGES += [email protected]
@@ -38,7 +41,13 @@
PRODUCT_PROPERTY_OVERRIDES += ro.hdmi.device_type=4
# Tuner HAL
-PRODUCT_PACKAGES += [email protected]
+PRODUCT_PACKAGES += android.hardware.tv.tuner-service.example
+
+# Sample Tuner Input for testing
+#PRODUCT_PACKAGES += LiveTv sampletunertvinput
+
+# Fallback IME and Home apps
+PRODUCT_PACKAGES += LeanbackIME TvSampleLeanbackLauncher TvProvision
# Enabling managed profiles
DEVICE_PACKAGE_OVERLAYS += device/google/cuttlefish/shared/tv/overlay
diff --git a/shared/tv/overlay/frameworks/base/core/res/res/values/config.xml b/shared/tv/overlay/frameworks/base/core/res/res/values/config.xml
index 46c64b7..195a8a7 100644
--- a/shared/tv/overlay/frameworks/base/core/res/res/values/config.xml
+++ b/shared/tv/overlay/frameworks/base/core/res/res/values/config.xml
@@ -17,8 +17,4 @@
<resources>
<!-- Maximum number of supported users -->
<integer name="config_multiuserMaximumUsers">4</integer>
- <!-- Restricting eth1 -->
- <string-array translatable="false" name="config_ethernet_interfaces">
- <item>eth1;11,12,14;;</item>
- </string-array>
</resources>
diff --git a/shared/wear/android-info.txt b/shared/wear/android-info.txt
new file mode 100644
index 0000000..22a5b5e
--- /dev/null
+++ b/shared/wear/android-info.txt
@@ -0,0 +1 @@
+config=wear
diff --git a/shared/wear/aosp_product.mk b/shared/wear/aosp_product.mk
new file mode 100644
index 0000000..ce6942e
--- /dev/null
+++ b/shared/wear/aosp_product.mk
@@ -0,0 +1,35 @@
+#
+# Copyright (C) 2022 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+$(call inherit-product, $(SRC_TARGET_DIR)/product/base_product.mk)
+
+# Default AOSP sounds
+$(call inherit-product-if-exists, frameworks/base/data/sounds/AllAudio.mk)
+
+# Wear pulls in some obsolete samples as well
+_ringtones := Callisto Dione Ganymede Luna Oberon Phobos Sedna Triton Umbriel
+PRODUCT_COPY_FILES += \
+ frameworks/base/data/sounds/alarms/ogg/Oxygen.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/alarms/Oxygen.ogg \
+ frameworks/base/data/sounds/notifications/ogg/Tethys.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/notifications/Tethys.ogg \
+ $(call product-copy-files-by-pattern,frameworks/base/data/sounds/ringtones/ogg/%.ogg,$(TARGET_COPY_OUT_PRODUCT)/media/audio/ringtones/%.ogg,$(_ringtones)) \
+
+PRODUCT_PRODUCT_PROPERTIES += \
+ ro.config.alarm_alert=Oxygen.ogg \
+ ro.config.notification_sound=Tethys.ogg \
+ ro.config.ringtone=Atria.ogg \
+
+PRODUCT_COPY_FILES += \
+ device/sample/etc/apns-full-conf.xml:$(TARGET_COPY_OUT_PRODUCT)/etc/apns-conf.xml \
diff --git a/shared/wear/aosp_system.mk b/shared/wear/aosp_system.mk
new file mode 100644
index 0000000..7e8f75e
--- /dev/null
+++ b/shared/wear/aosp_system.mk
@@ -0,0 +1,78 @@
+#
+# Copyright (C) 2022 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+OVERRIDE_TARGET_FLATTEN_APEX := true
+
+$(call inherit-product, $(SRC_TARGET_DIR)/product/base_system.mk)
+$(call inherit-product, $(SRC_TARGET_DIR)/product/languages_default.mk)
+$(call inherit-product-if-exists, external/hyphenation-patterns/patterns.mk)
+$(call inherit-product-if-exists, external/noto-fonts/fonts.mk)
+$(call inherit-product-if-exists, external/roboto-fonts/fonts.mk)
+$(call inherit-product-if-exists, frameworks/base/data/keyboards/keyboards.mk)
+$(call inherit-product-if-exists, frameworks/base/data/fonts/fonts.mk)
+$(call inherit-product-if-exists, vendor/google/security/adb/vendor_key.mk)
+
+PRODUCT_PACKAGES += \
+ BlockedNumberProvider \
+ Bluetooth \
+ CalendarProvider \
+ CertInstaller \
+ clatd \
+ clatd.conf \
+ DownloadProvider \
+ ethernet-service \
+ fsck.f2fs \
+ FusedLocation \
+ InputDevices \
+ KeyChain \
+ librs_jni \
+ ManagedProvisioning \
+ MmsService \
+ netutils-wrapper-1.0 \
+ screenrecord \
+ StatementService \
+ TelephonyProvider \
+ TeleService \
+ UserDictionaryProvider \
+
+PRODUCT_HOST_PACKAGES += \
+ fsck.f2fs \
+
+PRODUCT_SYSTEM_SERVER_APPS += \
+ FusedLocation \
+ InputDevices \
+ KeyChain \
+ Telecom \
+
+PRODUCT_SYSTEM_SERVER_JARS += \
+ services \
+ ethernet-service \
+
+PRODUCT_COPY_FILES += \
+ system/core/rootdir/etc/public.libraries.wear.txt:system/etc/public.libraries.txt \
+ system/core/rootdir/init.zygote32.rc:system/etc/init/hw/init.zygote32.rc \
+
+PRODUCT_USE_DYNAMIC_PARTITION_SIZE := true
+
+PRODUCT_ENFORCE_RRO_TARGETS := *
+
+PRODUCT_BRAND := generic
+
+PRODUCT_SYSTEM_NAME := mainline
+PRODUCT_SYSTEM_BRAND := Android
+PRODUCT_SYSTEM_MANUFACTURER := Android
+PRODUCT_SYSTEM_MODEL := mainline
+PRODUCT_SYSTEM_DEVICE := generic
diff --git a/vsoc_x86_noapex/BoardConfig.mk b/shared/wear/aosp_system_ext.mk
similarity index 73%
rename from vsoc_x86_noapex/BoardConfig.mk
rename to shared/wear/aosp_system_ext.mk
index 934b3e9..2fade9a 100644
--- a/vsoc_x86_noapex/BoardConfig.mk
+++ b/shared/wear/aosp_system_ext.mk
@@ -1,5 +1,5 @@
#
-# Copyright 2019 The Android Open-Source Project
+# Copyright (C) 2022 The Android Open Source Project
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
@@ -14,10 +14,6 @@
# limitations under the License.
#
-#
-# x86 target for Cuttlefish that doesn't support APEX.
-#
+$(call inherit-product, $(SRC_TARGET_DIR)/product/base_system_ext.mk)
-include device/google/cuttlefish/vsoc_x86/BoardConfig.mk
-
-TARGET_FLATTEN_APEX := true
+PRODUCT_PACKAGES += CarrierConfig
diff --git a/shared/wear/aosp_vendor.mk b/shared/wear/aosp_vendor.mk
new file mode 100644
index 0000000..18727f6
--- /dev/null
+++ b/shared/wear/aosp_vendor.mk
@@ -0,0 +1,43 @@
+#
+# Copyright (C) 2022 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+PRODUCT_VENDOR_PROPERTIES += \
+ ro.config.low_ram=true \
+
+PRODUCT_SYSTEM_SERVER_COMPILER_FILTER := speed-profile
+
+PRODUCT_ALWAYS_PREOPT_EXTRACTED_APK := true
+
+PRODUCT_USE_PROFILE_FOR_BOOT_IMAGE := true
+PRODUCT_DEX_PREOPT_BOOT_IMAGE_PROFILE_LOCATION := frameworks/base/config/boot-image-profile.txt
+
+PRODUCT_ART_TARGET_INCLUDE_DEBUG_BUILD := false
+
+PRODUCT_PACKAGES += \
+ CellBroadcastAppPlatform \
+ CellBroadcastServiceModulePlatform \
+ com.android.tethering.inprocess \
+ InProcessNetworkStack \
+
+PRODUCT_MINIMIZE_JAVA_DEBUG_INFO := true
+
+ifneq (,$(filter eng, $(TARGET_BUILD_VARIANT)))
+ PRODUCT_DISABLE_SCUDO := true
+endif
+
+TARGET_SYSTEM_PROP += device/google/cuttlefish/shared/wear/wearable-1024.prop
+
+TARGET_VNDK_USE_CORE_VARIANT := true
diff --git a/shared/wear/device_vendor.mk b/shared/wear/device_vendor.mk
new file mode 100644
index 0000000..b37c775
--- /dev/null
+++ b/shared/wear/device_vendor.mk
@@ -0,0 +1,60 @@
+#
+# Copyright (C) 2022 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+PRODUCT_MANIFEST_FILES += device/google/cuttlefish/shared/config/product_manifest.xml
+SYSTEM_EXT_MANIFEST_FILES += device/google/cuttlefish/shared/config/system_ext_manifest.xml
+
+$(call inherit-product, $(SRC_TARGET_DIR)/product/handheld_vendor.mk)
+
+PRODUCT_COPY_FILES += \
+ frameworks/native/data/etc/android.software.backup.xml:$(TARGET_COPY_OUT_VENDOR)/etc/permissions/android.software.backup.xml \
+ frameworks/native/data/etc/android.software.connectionservice.xml:$(TARGET_COPY_OUT_VENDOR)/etc/permissions/android.software.connectionservice.xml \
+ frameworks/native/data/etc/android.software.device_admin.xml:$(TARGET_COPY_OUT_VENDOR)/etc/permissions/android.software.device_admin.xml \
+ frameworks/native/data/etc/wearable_core_hardware.xml:$(TARGET_COPY_OUT_VENDOR)/etc/permissions/wearable_core_hardware.xml \
+
+$(call inherit-product, device/google/cuttlefish/shared/device.mk)
+
+PRODUCT_VENDOR_PROPERTIES += \
+ keyguard.no_require_sim=true \
+ ro.cdma.home.operator.alpha=Android \
+ ro.cdma.home.operator.numeric=302780 \
+ ro.com.android.dataroaming=true \
+ ro.telephony.default_network=9 \
+
+TARGET_USES_CF_RILD ?= true
+ifeq ($(TARGET_USES_CF_RILD),true)
+$(call inherit-product, $(SRC_TARGET_DIR)/product/telephony_vendor.mk)
+PRODUCT_PACKAGES += \
+ libcuttlefish-ril-2 \
+ libcuttlefish-rild
+endif
+
+PRODUCT_COPY_FILES += \
+ frameworks/native/data/etc/android.hardware.audio.output.xml:$(TARGET_COPY_OUT_VENDOR)/etc/permissions/android.hardware.audio.output.xml \
+ frameworks/native/data/etc/android.hardware.faketouch.xml:$(TARGET_COPY_OUT_VENDOR)/etc/permissions/android.hardware.faketouch.xml \
+ frameworks/native/data/etc/android.hardware.location.xml:$(TARGET_COPY_OUT_VENDOR)/etc/permissions/android.hardware.location.xml \
+ frameworks/native/data/etc/android.hardware.telephony.gsm.xml:$(TARGET_COPY_OUT_VENDOR)/etc/permissions/android.hardware.telephony.gsm.xml \
+ frameworks/native/data/etc/android.hardware.telephony.ims.xml:$(TARGET_COPY_OUT_VENDOR)/etc/permissions/android.hardware.telephony.ims.xml \
+
+# Runtime Resource Overlays
+PRODUCT_PACKAGES += \
+ cuttlefish_phone_overlay_frameworks_base_core \
+ cuttlefish_wear_overlay_frameworks_base_core \
+ cuttlefish_wear_overlay_settings_provider \
+
+PRODUCT_CHARACTERISTICS := nosdcard,watch
+
+TARGET_BOARD_INFO_FILE ?= device/google/cuttlefish/shared/wear/android-info.txt
diff --git a/shared/wear/overlays/SettingsProvider/Android.bp b/shared/wear/overlays/SettingsProvider/Android.bp
new file mode 100644
index 0000000..e6bde39
--- /dev/null
+++ b/shared/wear/overlays/SettingsProvider/Android.bp
@@ -0,0 +1,8 @@
+package {
+ default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+runtime_resource_overlay {
+ name: "cuttlefish_wear_overlay_settings_provider",
+ soc_specific: true,
+}
diff --git a/shared/wear/overlays/SettingsProvider/AndroidManifest.xml b/shared/wear/overlays/SettingsProvider/AndroidManifest.xml
new file mode 100644
index 0000000..273d67b
--- /dev/null
+++ b/shared/wear/overlays/SettingsProvider/AndroidManifest.xml
@@ -0,0 +1,11 @@
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.providers.settings.cuttlefish.wear.overlay">
+
+ <application android:hasCode="false" />
+
+ <overlay
+ android:targetPackage="com.android.providers.settings"
+ android:isStatic="true"
+ android:priority="3"
+ />
+</manifest>
diff --git a/shared/wear/overlays/SettingsProvider/res/values/defaults.xml b/shared/wear/overlays/SettingsProvider/res/values/defaults.xml
new file mode 100644
index 0000000..b02a9af
--- /dev/null
+++ b/shared/wear/overlays/SettingsProvider/res/values/defaults.xml
@@ -0,0 +1,38 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+** Copyright 2022, The Android Open Source Project.
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+** http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+-->
+<resources>
+ <bool name="def_accelerometer_rotation">false</bool>
+
+ <integer name="def_screen_off_timeout">15000</integer>
+ <bool name="def_lockscreen_disabled">false</bool>
+ <bool name="def_sound_effects_enabled">false</bool>
+ <integer name="def_power_sounds_enabled">0</integer>
+ <integer name="def_lockscreen_sounds_enabled">0</integer>
+ <integer name="def_dock_sounds_enabled">0</integer>
+ <integer name="def_dock_sounds_enabled_when_accessibility">1</integer>
+ <string name="def_location_providers_allowed" translatable="false">gps,network</string>
+ <string name="def_bluetooth_disabled_profiles">65536</string>
+ <bool name="def_accessibility_speak_password">true</bool>
+ <bool name="def_auto_time">false</bool>
+ <bool name="def_auto_time_zone">false</bool>
+ <bool name="def_mobile_data_always_on">false</bool>
+ <bool name="def_wifi_on">true</bool>
+ <bool name="def_wifi_wakeup_enabled">false</bool>
+ <bool name="def_vibrate_when_ringing">true</bool>
+</resources>
diff --git a/shared/wear/overlays/core/Android.bp b/shared/wear/overlays/core/Android.bp
new file mode 100644
index 0000000..2f522a9
--- /dev/null
+++ b/shared/wear/overlays/core/Android.bp
@@ -0,0 +1,8 @@
+package {
+ default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+runtime_resource_overlay {
+ name: "cuttlefish_wear_overlay_frameworks_base_core",
+ soc_specific: true,
+}
diff --git a/shared/wear/overlays/core/AndroidManifest.xml b/shared/wear/overlays/core/AndroidManifest.xml
new file mode 100644
index 0000000..c4ae538
--- /dev/null
+++ b/shared/wear/overlays/core/AndroidManifest.xml
@@ -0,0 +1,11 @@
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="android.cuttlefish.wear.overlay">
+
+ <application android:hasCode="false" />
+
+ <overlay
+ android:targetPackage="android"
+ android:isStatic="true"
+ android:priority="3"
+ />
+</manifest>
diff --git a/shared/wear/overlays/core/res/values/config.xml b/shared/wear/overlays/core/res/values/config.xml
new file mode 100644
index 0000000..a07169f
--- /dev/null
+++ b/shared/wear/overlays/core/res/values/config.xml
@@ -0,0 +1,116 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+** Copyright 2022, The Android Open Source Project.
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+** http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+-->
+<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string-array name="networkAttributes" translatable="false">
+ <item>"mobile,0,0,0,-1,true"</item>
+ <item>"wifi,1,1,1,-1,true"</item>
+ <item>"mobile_mms,2,0,2,60000,true"</item>
+ <item>"mobile_hipri,5,0,3,60000,true"</item>
+ <item>"bluetooth,7,7,2,-1,true"</item>
+ <item>"proxy,16,16,1,-1,true"</item>
+ </string-array>
+ <string-array name="radioAttributes" translatable="false">
+ <item>"0,1"</item>
+ <item>"1,1"</item>
+ <item>"4,1"</item>
+ <item>"7,1"</item>
+ <item>"11,1"</item>
+ <item>"16,1"</item>
+ </string-array>
+ <bool name="config_sms_capable">false</bool>
+ <bool name="config_showNavigationBar" translatable="false">false</bool>
+
+ <integer name="config_networkTransitionTimeout">0</integer>
+ <bool name="config_voice_capable">true</bool>
+ <bool name="config_requireCallCapableAccountForHandle">true</bool>
+ <bool name="config_enableWallpaperService">true</bool>
+ <bool name="config_dreamsSupported">false</bool>
+ <!--<bool name="config_suspendWhenScreenOffDueToProximity">true</bool>-->
+ <!--<bool name="config_powerDecoupleAutoSuspendModeFromDisplay">true</bool>-->
+ <!--<bool name="config_powerDecoupleInteractiveModeFromDisplay">true</bool>-->
+ <bool name="config_allowTheaterModeWakeFromPowerKey">true</bool>
+ <bool name="config_allowTheaterModeWakeFromKey">true</bool>
+ <bool name="config_supportAutoRotation">true</bool>
+ <bool name="config_hasRecents">false</bool>
+ <integer name="config_minimumScreenOffTimeout">5000</integer>
+ <integer name="config_maximumScreenDimDuration">300</integer>
+ <fraction name="config_maximumScreenDimRatio">20%</fraction>
+ <integer name="config_notificationServiceArchiveSize">1</integer>
+ <bool name="config_networkSamplingWakesDevice">false</bool>
+ <bool name="config_goToSleepOnButtonPressTheaterMode">false</bool>
+ <integer name="config_triplePressOnPowerBehavior">0</integer>
+ <integer name="config_shortPressOnPowerBehavior">5</integer>
+ <bool name="config_supportLongPressPowerWhenNonInteractive">true</bool>
+ <integer name="config_longPressOnPowerBehavior">0</integer>
+ <integer name="config_veryLongPressOnPowerBehavior">1</integer>
+ <bool name="config_allowStartActivityForLongPressOnPowerInSetup">true</bool>
+ <bool name="config_enableNetworkLocationOverlay" translatable="false">false</bool>
+ <bool name="config_allowAnimationsInLowPowerMode">true</bool>
+ <bool name="config_enableAutoPowerModes">true</bool>
+ <bool name="config_autoPowerModePreferWristTilt">true</bool>
+ <bool name="config_autoPowerModePrefetchLocation">false</bool>
+ <integer name="config_autoPowerModeThresholdAngle">10</integer>
+ <integer name="config_lowMemoryKillerMinFreeKbytesAbsolute">65536</integer>
+ <integer name="config_extraFreeKbytesAbsolute">14100</integer>
+ <integer name="config_previousVibrationsDumpLimit">100</integer>
+ <bool name="config_allowPriorityVibrationsInLowPowerMode">true</bool>
+ <bool name="config_cameraDoubleTapPowerGestureEnabled">false</bool>
+ <string name="config_icon_mask" translatable="false">"M50 0C77.6 0 100 22.4 100 50C100 77.6 77.6 100 50 100C22.4 100 0 77.6 0 50C0 22.4 22.4 0 50 0Z"</string>
+ <bool name="config_useRoundIcon">true</bool>
+ <integer translatable="false" name="config_brightness_ramp_rate_fast">400</integer>
+ <integer translatable="false" name="config_brightness_ramp_rate_slow">400</integer>
+ <integer name="config_autoBrightnessInitialLightSensorRate">100</integer>
+ <bool name="config_skipScreenOnBrightnessRamp">true</bool>
+ <integer-array name="config_longPressVibePattern">
+ <item>1</item>
+ <item>0</item>
+ </integer-array>
+ <bool name="config_batterySaverStickyBehaviourDisabled">true</bool>
+ <bool name="config_quickSettingsSupported">false</bool>
+ <integer name="config_defaultUiModeType">6</integer>
+ <bool name="config_lockUiMode">true</bool>
+ <integer name="config_longPressOnStemPrimaryBehavior">1</integer>
+ <integer name="config_doublePressOnStemPrimaryBehavior">1</integer>
+ <integer name="config_shortPressOnStemPrimaryBehavior">1</integer>
+ <integer name="config_triplePressOnStemPrimaryBehavior">1</integer>
+ <integer name="config_doublePressOnPowerBehavior">3</integer>
+ <integer name="config_veryLongPressTimeout">3000</integer>
+ <integer name="config_mashPressOnPowerBehavior">1</integer>
+ <integer name="config_mashPressVibrateTimeOnPowerButton">500</integer>
+ <item name="config_wallpaperMinScale" format="float" type="dimen">0</item>
+ <item name="config_wallpaperMaxScale" format="float" type="dimen">1</item>
+ <bool name="config_alwaysScaleWallpaper">true</bool>
+ <integer-array name="config_wakeup_based_screen_timeouts">
+ <item>0</item>
+ <item>0</item>
+ <item>0</item>
+ <item>0</item>
+ <item>0</item>
+ <item>0</item>
+ <item>0</item>
+ <item>0</item>
+ <item>0</item>
+ <item>0</item>
+ <item>6350</item>
+ </integer-array>
+ <bool name="config_preventTranslucentTaskTransitUpdateToActivity">true</bool>
+ <bool name="config_wearAllowAllAppsForegroundLocation">true</bool>
+ <bool name="config_telephonySingleSimDefaultSubscription">false</bool>
+ <bool name="config_disableTaskSnapshots">true</bool>
+</resources>
diff --git a/shared/wear/wear_excluded_hardware.xml b/shared/wear/wear_excluded_hardware.xml
new file mode 100644
index 0000000..e6f4be9
--- /dev/null
+++ b/shared/wear/wear_excluded_hardware.xml
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright 2022 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<permissions>
+ <unavailable-feature name="android.hardware.usb.accessory" />
+ <unavailable-feature name="android.hardware.usb.host" />
+</permissions>
diff --git a/shared/wear/wearable-1024.prop b/shared/wear/wearable-1024.prop
new file mode 100644
index 0000000..d13ff5d
--- /dev/null
+++ b/shared/wear/wearable-1024.prop
@@ -0,0 +1,19 @@
+ro.lmk.critical_upgrade=true
+ro.lmk.upgrade_pressure=40
+ro.lmk.downgrade_pressure=60
+ro.lmk.kill_heaviest_task=false
+
+pm.dexopt.bg-dexopt=speed-profile
+pm.dexopt.downgrade_after_inactive_days=10
+pm.dexopt.first-boot=verify
+pm.dexopt.install=quicken
+pm.dexopt.shared=quicken
+
+dalvik.vm.dex2oat-swap=true
+dalvik.vm.heapstartsize=8m
+dalvik.vm.heapgrowthlimit=96m
+dalvik.vm.heapsize=128m
+dalvik.vm.heaptargetutilization=0.75
+dalvik.vm.heapminfree=512k
+dalvik.vm.heapmaxfree=8m
+dalvik.vm.foreground-heap-growth-multiplier=2.0
diff --git a/tests/hal/hal_implementation_test.cpp b/tests/hal/hal_implementation_test.cpp
index d05f077..eaefd87 100644
--- a/tests/hal/hal_implementation_test.cpp
+++ b/tests/hal/hal_implementation_test.cpp
@@ -17,15 +17,16 @@
#include <android-base/logging.h>
#include <android-base/strings.h>
#include <gtest/gtest.h>
-#include <hidl/metadata.h>
#include <hidl-util/FQName.h>
+#include <hidl/metadata.h>
#include <vintf/VintfObject.h>
using namespace android;
+// clang-format off
static const std::set<std::string> kKnownMissingHidl = {
- "[email protected]",
"[email protected]",
+ "[email protected]", // deprecated, see b/141930622
"[email protected]", // deprecated, see b/37226359
"[email protected]",
"[email protected]",
@@ -47,22 +48,32 @@
"[email protected]",
"[email protected]",
"[email protected]",
+ "[email protected]", // converted to AIDL, see b/203490261
"[email protected]",
"[email protected]",
+ "[email protected]", // Camera converted to AIDL, b/196432585
"[email protected]",
- "[email protected]",
"[email protected]", // deprecated, see b/149050985, b/149050733
+ "[email protected]",
+ "[email protected]", // converted to AIDL, b/200055138
"[email protected]",
+ "[email protected]", // deprecated, see b/205760700
+ "[email protected]", // GNSS converted to AIDL, b/206670536
+ "[email protected]", // GNSS converted to AIDL, b/206670536
"[email protected]", // is sub-interface of gnss
"[email protected]",
"[email protected]",
"[email protected]",
+ "[email protected]", // converted to AIDL, see b/205761012
"[email protected]",
"[email protected]",
+ "[email protected]", // converted to AIDL, see b/193240715
"[email protected]",
"[email protected]",
"[email protected]", // converted to AIDL, see b/177470478
- "[email protected]",
+ "[email protected]", // converted to AIDL, see b/177269435
+ "[email protected]", // converted to AIDL, see b/205761620
+ "[email protected]", // converted to AIDL, see b/205000342
"[email protected]",
"[email protected]", // Replaced by KeyMint
"[email protected]",
@@ -73,235 +84,348 @@
"[email protected]",
"[email protected]",
"[email protected]",
+ "[email protected]", // converted to AIDL
+ "[email protected]", // converted to AIDL
"[email protected]",
"[email protected]",
"[email protected]",
"[email protected]",
"[email protected]",
+ "[email protected]",
"[email protected]",
"[email protected]", // see b/170699770
"[email protected]",
"[email protected]",
"[email protected]",
"[email protected]",
- "[email protected]",
+ "[email protected]", // migrated to AIDL see b/200993386
"[email protected]",
"[email protected]",
"[email protected]",
"[email protected]",
- "[email protected]",
"[email protected]",
+ "[email protected]",
"[email protected]",
"[email protected]",
"[email protected]",
+ "[email protected]", // Converted to AIDL (see b/170260236)
+};
+// clang-format on
+
+struct VersionedAidlPackage {
+ std::string name;
+ size_t version;
+ bool operator<(const VersionedAidlPackage& rhs) const {
+ return (name < rhs.name || (name == rhs.name && version < rhs.version));
+ }
};
-static const std::set<std::string> kKnownMissingAidl = {
+static const std::set<VersionedAidlPackage> kKnownMissingAidl = {
+ // Cuttlefish Identity Credential HAL implementation is currently
+ // stuck at version 3 while RKP support is being added. Will be
+ // updated soon.
+ {"android.hardware.identity.", 4},
+
// types-only packages, which never expect a default implementation
- "android.hardware.biometrics.common.",
- "android.hardware.common.",
- "android.hardware.common.fmq.",
- "android.hardware.graphics.common.",
+ {"android.hardware.audio.common.", 1},
+ {"android.hardware.biometrics.common.", 1},
+ {"android.hardware.biometrics.common.", 2},
+ {"android.hardware.common.", 1},
+ {"android.hardware.common.", 2},
+ {"android.hardware.common.fmq.", 1},
+
+ {"android.hardware.graphics.common.", 1},
+ {"android.hardware.graphics.common.", 2},
+ {"android.hardware.graphics.common.", 3},
+ {"android.hardware.input.common.", 1},
+
+ // android.hardware.camera.device is an interface returned by
+ // android.hardware.camera.provider.
+ // android.hardware.camera.common and android.hardware.camera.metadata are
+ // types used by android.hardware.camera.provider and
+ // android.hardware.camera.device.
+ {"android.hardware.camera.common.", 1},
+ {"android.hardware.camera.device.", 1},
+ {"android.hardware.camera.metadata.", 1},
+
+ // No implementations on cuttlefish for omapi aidl hal
+ {"android.se.omapi.", 1},
// These KeyMaster types are in an AIDL types-only HAL because they're used
// by the Identity Credential AIDL HAL. Remove this when fully porting
// KeyMaster to AIDL.
- "android.hardware.keymaster.",
+ {"android.hardware.keymaster.", 1},
+ {"android.hardware.keymaster.", 2},
+ {"android.hardware.keymaster.", 3},
+
+ // Sound trigger doesn't have a default implementation.
+ {"android.hardware.soundtrigger3.", 1},
+ {"android.media.soundtrigger.", 1},
+ {"android.media.audio.common.", 1},
// These types are only used in Automotive.
- "android.automotive.computepipe.registry.",
- "android.automotive.computepipe.runner.",
- "android.automotive.watchdog.",
- "android.frameworks.automotive.powerpolicy.",
- "android.frameworks.automotive.telemetry.",
- "android.hardware.automotive.audiocontrol.",
- "android.hardware.automotive.occupant_awareness.",
+ {"android.automotive.computepipe.registry.", 1},
+ {"android.automotive.computepipe.runner.", 1},
+ {"android.automotive.watchdog.", 2},
+ {"android.automotive.watchdog.", 3},
+ {"android.frameworks.automotive.display.", 1},
+ {"android.frameworks.automotive.powerpolicy.", 1},
+ {"android.frameworks.automotive.powerpolicy.internal.", 1},
+ {"android.frameworks.automotive.telemetry.", 1},
+ {"android.hardware.automotive.audiocontrol.", 1},
+ {"android.hardware.automotive.audiocontrol.", 2},
+ {"android.hardware.automotive.evs.", 1},
+ {"android.hardware.automotive.occupant_awareness.", 1},
+ {"android.hardware.automotive.vehicle.", 1},
+
+ // These types are only used in TV.
+ {"android.hardware.tv.tuner.", 1},
+
+ // types-only packages, which never expect a default implementation
+ {"android.hardware.radio.", 1},
+
+ // types-only packages, which never expect a default implementation
+ {"android.hardware.uwb.fira_android.", 1},
+};
+
+static const std::set<VersionedAidlPackage> kComingSoonAidl = {
};
// AOSP packages which are never considered
static bool isHidlPackageConsidered(const FQName& name) {
- static std::vector<std::string> gAospExclude = {
- // packages not implemented now that we never expect to be implemented
- "android.hardware.tests",
- // packages not registered with hwservicemanager, usually sub-interfaces
- "android.hardware.camera.device",
- };
- for (const std::string& package : gAospExclude) {
- if (name.inPackage(package)) {
- return false;
- }
+ static std::vector<std::string> gAospExclude = {
+ // packages not implemented now that we never expect to be implemented
+ "android.hardware.tests",
+ // packages not registered with hwservicemanager, usually sub-interfaces
+ "android.hardware.camera.device",
+ };
+ for (const std::string& package : gAospExclude) {
+ if (name.inPackage(package)) {
+ return false;
}
- return true;
+ }
+ return true;
}
static bool isAospHidlInterface(const FQName& name) {
- static const std::vector<std::string> kAospPackages = {
- "android.hidl",
- "android.hardware",
- "android.frameworks",
- "android.system",
- };
- for (const std::string& package : kAospPackages) {
- if (name.inPackage(package)) {
- return true;
- }
+ static const std::vector<std::string> kAospPackages = {
+ "android.hidl",
+ "android.hardware",
+ "android.frameworks",
+ "android.system",
+ };
+ for (const std::string& package : kAospPackages) {
+ if (name.inPackage(package)) {
+ return true;
}
- return false;
+ }
+ return false;
}
static std::set<FQName> allTreeHidlInterfaces() {
- std::set<FQName> ret;
- for (const auto& iface : HidlInterfaceMetadata::all()) {
- FQName f;
- CHECK(f.setTo(iface.name)) << iface.name;
- ret.insert(f);
- }
- return ret;
+ std::set<FQName> ret;
+ for (const auto& iface : HidlInterfaceMetadata::all()) {
+ FQName f;
+ CHECK(f.setTo(iface.name)) << iface.name;
+ ret.insert(f);
+ }
+ return ret;
}
static std::set<FQName> allHidlManifestInterfaces() {
- std::set<FQName> ret;
- auto setInserter = [&] (const vintf::ManifestInstance& i) -> bool {
- if (i.format() != vintf::HalFormat::HIDL) {
- return true; // continue
- }
- ret.insert(i.getFqInstance().getFqName());
- return true; // continue
- };
- vintf::VintfObject::GetDeviceHalManifest()->forEachInstance(setInserter);
- vintf::VintfObject::GetFrameworkHalManifest()->forEachInstance(setInserter);
- return ret;
+ std::set<FQName> ret;
+ auto setInserter = [&](const vintf::ManifestInstance& i) -> bool {
+ if (i.format() != vintf::HalFormat::HIDL) {
+ return true; // continue
+ }
+ ret.insert(i.getFqInstance().getFqName());
+ return true; // continue
+ };
+ vintf::VintfObject::GetDeviceHalManifest()->forEachInstance(setInserter);
+ vintf::VintfObject::GetFrameworkHalManifest()->forEachInstance(setInserter);
+ return ret;
}
static bool isAospAidlInterface(const std::string& name) {
- return base::StartsWith(name, "android.") &&
- !base::StartsWith(name, "android.hardware.tests.") &&
- !base::StartsWith(name, "android.aidl.tests");
+ return base::StartsWith(name, "android.") &&
+ !base::StartsWith(name, "android.hardware.tests.") &&
+ !base::StartsWith(name, "android.aidl.tests");
}
-static std::set<std::string> allAidlManifestInterfaces() {
- std::set<std::string> ret;
- auto setInserter = [&] (const vintf::ManifestInstance& i) -> bool {
- if (i.format() != vintf::HalFormat::AIDL) {
- return true; // continue
- }
- ret.insert(i.package() + "." + i.interface());
- return true; // continue
- };
- vintf::VintfObject::GetDeviceHalManifest()->forEachInstance(setInserter);
- vintf::VintfObject::GetFrameworkHalManifest()->forEachInstance(setInserter);
- return ret;
+static std::set<VersionedAidlPackage> allAidlManifestInterfaces() {
+ std::set<VersionedAidlPackage> ret;
+ auto setInserter = [&](const vintf::ManifestInstance& i) -> bool {
+ if (i.format() != vintf::HalFormat::AIDL) {
+ return true; // continue
+ }
+ ret.insert({i.package() + "." + i.interface(), i.version().minorVer});
+ return true; // continue
+ };
+ vintf::VintfObject::GetDeviceHalManifest()->forEachInstance(setInserter);
+ vintf::VintfObject::GetFrameworkHalManifest()->forEachInstance(setInserter);
+ return ret;
}
TEST(Hal, AllHidlInterfacesAreInAosp) {
- for (const FQName& name : allHidlManifestInterfaces()) {
- EXPECT_TRUE(isAospHidlInterface(name))
- << "This device should only have AOSP interfaces, not: "
- << name.string();
- }
+ for (const FQName& name : allHidlManifestInterfaces()) {
+ EXPECT_TRUE(isAospHidlInterface(name))
+ << "This device should only have AOSP interfaces, not: "
+ << name.string();
+ }
}
TEST(Hal, HidlInterfacesImplemented) {
- // instances -> major version -> minor versions
- std::map<std::string, std::map<size_t, std::set<size_t>>> unimplemented;
+ // instances -> major version -> minor versions
+ std::map<std::string, std::map<size_t, std::set<size_t>>> unimplemented;
- for (const FQName& f : allTreeHidlInterfaces()) {
- if (!isAospHidlInterface(f)) continue;
- if (!isHidlPackageConsidered(f)) continue;
+ for (const FQName& f : allTreeHidlInterfaces()) {
+ if (!isAospHidlInterface(f)) continue;
+ if (!isHidlPackageConsidered(f)) continue;
- unimplemented[f.package()][f.getPackageMajorVersion()].insert(f.getPackageMinorVersion());
+ unimplemented[f.package()][f.getPackageMajorVersion()].insert(
+ f.getPackageMinorVersion());
+ }
+
+ // we'll be removing items from this which we know are missing
+ // in order to be left with those elements which we thought we
+ // knew were missing but are actually present
+ std::set<std::string> thoughtMissing = kKnownMissingHidl;
+
+ for (const FQName& f : allHidlManifestInterfaces()) {
+ if (thoughtMissing.erase(f.getPackageAndVersion().string()) > 0) {
+ ADD_FAILURE() << "Instance in missing list, but available: "
+ << f.string();
}
- // we'll be removing items from this which we know are missing
- // in order to be left with those elements which we thought we
- // knew were missing but are actually present
- std::set<std::string> thoughtMissing = kKnownMissingHidl;
+ std::set<size_t>& minors =
+ unimplemented[f.package()][f.getPackageMajorVersion()];
+ size_t minor = f.getPackageMinorVersion();
- for (const FQName& f : allHidlManifestInterfaces()) {
- if (thoughtMissing.erase(f.getPackageAndVersion().string()) > 0) {
- ADD_FAILURE() << "Instance in missing list, but available: " << f.string();
- }
+ auto it = minors.find(minor);
+ if (it == minors.end()) continue;
- std::set<size_t>& minors = unimplemented[f.package()][f.getPackageMajorVersion()];
- size_t minor = f.getPackageMinorVersion();
+ // if 1.2 is implemented, also considere 1.0, 1.1 implemented
+ minors.erase(minors.begin(), std::next(it));
+ }
- auto it = minors.find(minor);
- if (it == minors.end()) continue;
+ for (const auto& [package, minorsPerMajor] : unimplemented) {
+ for (const auto& [major, minors] : minorsPerMajor) {
+ if (minors.empty()) continue;
- // if 1.2 is implemented, also considere 1.0, 1.1 implemented
- minors.erase(minors.begin(), std::next(it));
+ size_t maxMinor = *minors.rbegin();
+
+ FQName missing;
+ ASSERT_TRUE(missing.setTo(package, major, maxMinor));
+
+ if (thoughtMissing.erase(missing.string()) > 0) continue;
+
+ ADD_FAILURE() << "Missing implementation from " << missing.string();
}
+ }
- for (const auto& [package, minorsPerMajor] : unimplemented) {
- for (const auto& [major, minors] : minorsPerMajor) {
- if (minors.empty()) continue;
-
- size_t maxMinor = *minors.rbegin();
-
- FQName missing;
- ASSERT_TRUE(missing.setTo(package, major, maxMinor));
-
- if (thoughtMissing.erase(missing.string()) > 0) continue;
-
- ADD_FAILURE() << "Missing implementation from " << missing.string();
- }
- }
-
- for (const std::string& missing : thoughtMissing) {
- ADD_FAILURE() << "Instance in missing list and cannot find it anywhere: " << missing
- << " (multiple versions in missing list?)";
- }
+ for (const std::string& missing : thoughtMissing) {
+ ADD_FAILURE() << "Instance in missing list and cannot find it anywhere: "
+ << missing << " (multiple versions in missing list?)";
+ }
}
TEST(Hal, AllAidlInterfacesAreInAosp) {
- for (const std::string& name : allAidlManifestInterfaces()) {
- EXPECT_TRUE(isAospAidlInterface(name))
- << "This device should only have AOSP interfaces, not: " << name;
- }
+ for (const auto& package : allAidlManifestInterfaces()) {
+ EXPECT_TRUE(isAospAidlInterface(package.name))
+ << "This device should only have AOSP interfaces, not: "
+ << package.name;
+ }
}
// android.hardware.foo.IFoo -> android.hardware.foo.
std::string getAidlPackage(const std::string& aidlType) {
- size_t lastDot = aidlType.rfind('.');
- CHECK(lastDot != std::string::npos);
- return aidlType.substr(0, lastDot + 1);
+ size_t lastDot = aidlType.rfind('.');
+ CHECK(lastDot != std::string::npos);
+ return aidlType.substr(0, lastDot + 1);
}
+struct AidlPackageCheck {
+ bool hasRegistration;
+ bool knownMissing;
+};
+
TEST(Hal, AidlInterfacesImplemented) {
- std::set<std::string> manifest = allAidlManifestInterfaces();
- std::set<std::string> thoughtMissing = kKnownMissingAidl;
+ std::set<VersionedAidlPackage> manifest = allAidlManifestInterfaces();
+ std::set<VersionedAidlPackage> thoughtMissing = kKnownMissingAidl;
+ std::set<VersionedAidlPackage> comingSoon = kComingSoonAidl;
- for (const auto& iface : AidlInterfaceMetadata::all()) {
- ASSERT_FALSE(iface.types.empty()) << iface.name; // sanity
- if (std::none_of(iface.types.begin(), iface.types.end(), isAospAidlInterface)) continue;
- if (iface.stability != "vintf") continue;
+ for (const auto& treePackage : AidlInterfaceMetadata::all()) {
+ ASSERT_FALSE(treePackage.types.empty()) << treePackage.name;
+ if (std::none_of(treePackage.types.begin(), treePackage.types.end(),
+ isAospAidlInterface))
+ continue;
+ if (treePackage.stability != "vintf") continue;
- bool hasRegistration = false;
- bool knownMissing = false;
- for (const std::string& type : iface.types) {
- if (manifest.erase(type) > 0) hasRegistration = true;
- if (thoughtMissing.erase(getAidlPackage(type)) > 0) knownMissing = true;
+ // expect versions from 1 to latest version. If the package has development
+ // the latest version is the latest known version + 1. Each of these need
+ // to be checked for registration and knownMissing.
+ std::map<size_t, AidlPackageCheck> expectedVersions;
+ for (const auto version : treePackage.versions) {
+ expectedVersions[version] = {false, false};
+ }
+ if (treePackage.has_development) {
+ size_t version =
+ treePackage.versions.empty() ? 1 : *treePackage.versions.rbegin() + 1;
+ expectedVersions[version] = {false, false};
+ }
+
+ // Check all types and versions defined by the package for registration.
+ // The package version is considered registered if any of those types are
+ // present in the manifest with the same version.
+ // The package version is considered known missing if it is found in
+ // thoughtMissing.
+ bool latestRegistered = false;
+ for (const std::string& type : treePackage.types) {
+ for (auto& [version, check] : expectedVersions) {
+ if (manifest.erase({type, version}) > 0) {
+ if (version == expectedVersions.rbegin()->first) {
+ latestRegistered = true;
+ }
+ check.hasRegistration = true;
+ }
+ if (thoughtMissing.erase({getAidlPackage(type), version}) > 0)
+ check.knownMissing = true;
+ }
+ }
+
+ if (!latestRegistered && !expectedVersions.rbegin()->second.knownMissing) {
+ ADD_FAILURE() << "The latest version ("
+ << expectedVersions.rbegin()->first
+ << ") of the package is not implemented: "
+ << treePackage.name
+ << " which declares the following types:\n "
+ << base::Join(treePackage.types, "\n ");
+ }
+
+ for (const auto& [version, check] : expectedVersions) {
+ if (check.knownMissing) {
+ if (check.hasRegistration) {
+ ADD_FAILURE() << "Package in missing list, but available: "
+ << treePackage.name << " V" << version
+ << " which declares the following types:\n "
+ << base::Join(treePackage.types, "\n ");
}
- if (knownMissing) {
- if (hasRegistration) {
- ADD_FAILURE() << "Interface in missing list, but available: " << iface.name
- << " which declares the following types:\n "
- << base::Join(iface.types, "\n ");
- }
-
- continue;
- }
-
- EXPECT_TRUE(hasRegistration) << iface.name << " which declares the following types:\n "
- << base::Join(iface.types, "\n ");
+ continue;
+ }
}
+ }
- for (const std::string& iface : thoughtMissing) {
- ADD_FAILURE() << "Interface in manifest list and cannot find it anywhere: " << iface;
+ for (const auto& package : thoughtMissing) {
+ // TODO: b/194806512 : Remove after Wifi hostapd AIDL interface lands on aosp
+ if (comingSoon.erase(package) == 0) {
+ ADD_FAILURE() << "Interface in missing list and cannot find it anywhere: "
+ << package.name << " V" << package.version;
}
+ }
- for (const std::string& iface : manifest) {
- ADD_FAILURE() << "Can't find manifest entry in tree: " << iface;
- }
+ for (const auto& package : manifest) {
+ ADD_FAILURE() << "Can't find manifest entry in tree: " << package.name
+ << " version: " << package.version;
+ }
}
diff --git a/tests/integration/Android.bp b/tests/integration/Android.bp
new file mode 100644
index 0000000..89ef7b5
--- /dev/null
+++ b/tests/integration/Android.bp
@@ -0,0 +1,44 @@
+//
+// Copyright (C) 2022 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package {
+ default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+java_test_host {
+ name: "CuttlefishIntegrationTest",
+
+ auto_gen_config: false, // Use the AndroidTest.xml file in this directory
+ test_config: "AndroidTest.xml",
+
+ srcs: ["com/**/*.java"],
+
+ data_native_bins: ["cvd_test_gce_driver"],
+
+ libs: [
+ "auto_value_annotations",
+ "libprotobuf-java-full",
+ "guava",
+ "guice-no-guava",
+ "junit",
+ "tradefed",
+ ],
+ static_libs: [
+ "libcuttlefish_test_gce_proto_java",
+ ],
+
+ plugins: ["auto_value_plugin", "auto_annotation_plugin"],
+ test_suites: ["general-tests"],
+}
diff --git a/tests/integration/AndroidTest.xml b/tests/integration/AndroidTest.xml
new file mode 100644
index 0000000..7885ad1
--- /dev/null
+++ b/tests/integration/AndroidTest.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2020 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<configuration description="Runs CuttlefishIntegrationTest">
+ <option name="test-suite-tag" value="cuttlefish_integration_test" />
+ <test class="com.android.tradefed.testtype.HostTest" >
+ <option name="jar" value="CuttlefishIntegrationTest.jar" />
+ </test>
+</configuration>
diff --git a/tests/integration/com/android/cuttlefish/test/AcloudLocalInstanceTest.java b/tests/integration/com/android/cuttlefish/test/AcloudLocalInstanceTest.java
new file mode 100644
index 0000000..9fd11a3
--- /dev/null
+++ b/tests/integration/com/android/cuttlefish/test/AcloudLocalInstanceTest.java
@@ -0,0 +1,74 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.cuttlefish.test;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assume.assumeNotNull;
+
+import com.google.inject.Inject;
+import java.io.File;
+import javax.annotation.Nullable;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(CuttlefishIntegrationTestRunner.class)
+public class AcloudLocalInstanceTest {
+ @Inject(optional = true)
+ @SetOption("gce-driver-service-account-json-key-path")
+ @Nullable
+ private String gceJsonKeyPath = null;
+
+ @Inject @Rule public GceInstanceRule gceInstance;
+ @Inject private BuildChooser buildChooser;
+
+ @Test
+ public void launchAcloudPrebuilt() throws Exception {
+ assumeNotNull(gceJsonKeyPath);
+ gceInstance.uploadFile(new File(gceJsonKeyPath), "key.json");
+ assertEquals(0, gceInstance.ssh("sudo", "apt-get", "install", "adb", "-y").returnCode());
+ gceInstance.uploadBuildArtifact("acloud_prebuilt", "acloud");
+ assertEquals(0, gceInstance.ssh("chmod", "+x", "acloud").returnCode());
+ // TODO(schuffelen): Make this choose the current build
+ assertEquals(0,
+ gceInstance
+ .ssh("./acloud", "create", "-y", "--local-instance", "--skip-pre-run-check",
+ "--service-account-json-private-key-path=key.json",
+ "--build-id=" + buildChooser.buildId(),
+ "--build-target=" + buildChooser.buildFlavor())
+ .returnCode());
+ }
+
+ @Test
+ public void launchAcloudDev() throws Exception {
+ assumeNotNull(gceJsonKeyPath);
+ gceInstance.uploadFile(new File(gceJsonKeyPath), "key.json");
+ assertEquals(0, gceInstance.ssh("sudo", "apt-get", "install", "adb", "-y").returnCode());
+ gceInstance.uploadBuildArtifact("acloud-dev", "acloud");
+ assertEquals(0, gceInstance.ssh("chmod", "+x", "acloud").returnCode());
+ // TODO(schuffelen): Make this choose the current build
+ assertEquals(0,
+ gceInstance
+ .ssh("./acloud", "create", "-y", "--local-instance", "--skip-pre-run-check",
+ "--service-account-json-private-key-path=key.json",
+ "--build-id=" + buildChooser.buildId(),
+ "--build-target=" + buildChooser.buildFlavor())
+ .returnCode());
+ }
+}
diff --git a/tests/integration/com/android/cuttlefish/test/BuildChooser.java b/tests/integration/com/android/cuttlefish/test/BuildChooser.java
new file mode 100644
index 0000000..38cc963
--- /dev/null
+++ b/tests/integration/com/android/cuttlefish/test/BuildChooser.java
@@ -0,0 +1,59 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.cuttlefish.test;
+
+import com.android.tradefed.build.IBuildInfo;
+import com.google.inject.Inject;
+import javax.annotation.Nullable;
+
+/**
+ * Manager for a dedicated GCE instance for every @Test function.
+ *
+ * Must be constructed through Guice injection. Calls out to the cvd_test_gce_driver binary to
+ * create the GCE instances.
+ */
+public final class BuildChooser {
+ @Inject(optional = true)
+ @SetOption("build-flavor")
+ @Nullable
+ private String buildFlavorOption = null;
+
+ @Inject(optional = true) @SetOption("build-id") @Nullable private String buildIdOption = null;
+
+ @Inject private IBuildInfo buildInfo;
+
+ public String fetchCvdBuild() {
+ return buildId() + "/" + buildFlavor();
+ }
+
+ public Build buildProto() {
+ return Build.newBuilder().setId(buildId()).setTarget(buildFlavor()).build();
+ }
+
+ public String buildFlavor() {
+ if (buildFlavorOption != null) {
+ return buildFlavorOption;
+ }
+ return buildInfo.getBuildFlavor();
+ }
+
+ public String buildId() {
+ if (buildIdOption != null) {
+ return buildIdOption;
+ }
+ return buildInfo.getBuildId();
+ }
+}
diff --git a/tests/integration/com/android/cuttlefish/test/CuttlefishIntegrationTestRunner.java b/tests/integration/com/android/cuttlefish/test/CuttlefishIntegrationTestRunner.java
new file mode 100644
index 0000000..5ba6f9f
--- /dev/null
+++ b/tests/integration/com/android/cuttlefish/test/CuttlefishIntegrationTestRunner.java
@@ -0,0 +1,122 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.cuttlefish.test;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+import static com.google.common.base.Preconditions.checkState;
+
+import com.android.tradefed.build.IBuildInfo;
+import com.android.tradefed.config.Option;
+import com.android.tradefed.invoker.TestInformation;
+import com.android.tradefed.testtype.HostTest;
+import com.android.tradefed.testtype.IBuildReceiver;
+import com.android.tradefed.testtype.ISetOptionReceiver;
+import com.android.tradefed.testtype.ITestInformationReceiver;
+import com.google.auto.value.AutoAnnotation;
+import com.google.common.collect.ImmutableMap;
+import com.google.inject.AbstractModule;
+import com.google.inject.Binder;
+import com.google.inject.Guice;
+import com.google.inject.Injector;
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.regex.Pattern;
+import org.junit.runners.BlockJUnit4ClassRunner;
+import org.junit.runners.model.InitializationError;
+import org.junit.runners.model.TestClass;
+
+public final class CuttlefishIntegrationTestRunner extends BlockJUnit4ClassRunner
+ implements ITestInformationReceiver, ISetOptionReceiver, IBuildReceiver {
+ @Option(name = HostTest.SET_OPTION_NAME, description = HostTest.SET_OPTION_DESC)
+ private HashSet<String> keyValueOptions = new HashSet<>();
+
+ private IBuildInfo buildInfo;
+ private TestInformation testInfo;
+ private final TestClass testClass;
+
+ // Required by JUnit
+ public CuttlefishIntegrationTestRunner(Class<?> testClass) throws InitializationError {
+ this(new TestClass(testClass));
+ }
+
+ private CuttlefishIntegrationTestRunner(TestClass testClass) throws InitializationError {
+ super(testClass);
+ this.testClass = testClass;
+ }
+
+ @Override
+ public void setBuild(IBuildInfo buildInfo) {
+ this.buildInfo = checkNotNull(buildInfo);
+ }
+
+ @Override
+ public void setTestInformation(TestInformation testInfo) {
+ this.testInfo = checkNotNull(testInfo);
+ }
+
+ @Override
+ public TestInformation getTestInformation() {
+ return checkNotNull(testInfo);
+ }
+
+ private ImmutableMap<String, String> processOptions() {
+ // Regex from HostTest.setOptionToLoadedObject
+ String delim = ":";
+ String esc = "\\";
+ String regex = "(?<!" + Pattern.quote(esc) + ")" + Pattern.quote(delim);
+ ImmutableMap.Builder<String, String> optMap = ImmutableMap.builder();
+ for (String item : keyValueOptions) {
+ String[] fields = item.split(regex);
+ checkState(fields.length == 2, "Could not parse \"%s\"", item);
+ String value = fields[1].replaceAll(Pattern.quote(esc) + Pattern.quote(delim), delim);
+ optMap.put(fields[0], value);
+ }
+ return optMap.build();
+ }
+
+ @AutoAnnotation
+ private static SetOption setOptionAnnotation(String value) {
+ return new AutoAnnotation_CuttlefishIntegrationTestRunner_setOptionAnnotation(value);
+ }
+
+ private void bindOptions(Binder binder) {
+ // TODO(schuffelen): Handle collections and maps
+ for (Map.Entry<String, String> option : processOptions().entrySet()) {
+ SetOption annotation = setOptionAnnotation(option.getKey());
+ binder.bind(String.class).annotatedWith(annotation).toInstance(option.getValue());
+ }
+ }
+
+ private final class TradefedClassesModule extends AbstractModule {
+ @Override
+ protected void configure() {
+ bind(TestInformation.class).toInstance(testInfo);
+ bind(IBuildInfo.class).toInstance(buildInfo);
+ bindOptions(binder());
+ }
+ }
+
+ @Override
+ protected Object createTest() {
+ Injector injector = Guice.createInjector(new TradefedClassesModule());
+ return injector.getInstance(testClass.getJavaClass());
+ }
+}
diff --git a/tests/integration/com/android/cuttlefish/test/GceInstanceRule.java b/tests/integration/com/android/cuttlefish/test/GceInstanceRule.java
new file mode 100644
index 0000000..a3889b5
--- /dev/null
+++ b/tests/integration/com/android/cuttlefish/test/GceInstanceRule.java
@@ -0,0 +1,288 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.cuttlefish.test;
+
+import static com.google.common.collect.ImmutableList.toImmutableList;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assume.assumeNoException;
+import static org.junit.Assume.assumeNotNull;
+import static org.junit.Assume.assumeTrue;
+
+import com.android.cuttlefish.test.DataType;
+import com.android.cuttlefish.test.Exit;
+import com.android.cuttlefish.test.TestMessage;
+import com.android.ddmlib.Log.LogLevel;
+import com.android.tradefed.invoker.TestInformation;
+import com.android.tradefed.log.LogUtil.CLog;
+import com.android.tradefed.util.FileUtil;
+import com.google.auto.value.AutoValue;
+import com.google.common.collect.ImmutableList;
+import com.google.inject.Inject;
+import com.google.protobuf.ByteString;
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.OutputStream;
+import java.util.UUID;
+import javax.annotation.Nullable;
+import org.junit.rules.TestRule;
+import org.junit.runner.Description;
+import org.junit.runners.model.Statement;
+
+/**
+ * Manager for a dedicated GCE instance for every @Test function.
+ *
+ * Must be constructed through Guice injection. Calls out to the cvd_test_gce_driver binary to
+ * create the GCE instances.
+ */
+public final class GceInstanceRule implements TestRule {
+ @Inject(optional = true)
+ @SetOption("gce-driver-service-account-json-key-path")
+ @Nullable
+ private String gceJsonKeyPath = null;
+
+ @Inject(optional = true) @SetOption("cloud-project") private String cloudProject;
+
+ @Inject(optional = true) @SetOption("zone") private String zone = "us-west1-a";
+
+ @Inject(optional = true)
+ @SetOption("internal-addresses")
+ private boolean internal_addresses = false;
+
+ @Inject private TestInformation testInfo;
+ @Inject private BuildChooser buildChooser;
+
+ private Process driverProcess;
+ private String managedInstance;
+
+ private Process launchDriver(File gceDriver) throws IOException {
+ ImmutableList.Builder<String> cmdline = new ImmutableList.Builder();
+ cmdline.add(gceDriver.toString());
+ assumeNotNull(gceJsonKeyPath);
+ cmdline.add("--internal-addresses=" + internal_addresses);
+ cmdline.add("--cloud-project=" + cloudProject);
+ cmdline.add("--service-account-json-private-key-path=" + gceJsonKeyPath);
+ ProcessBuilder processBuilder = new ProcessBuilder(cmdline.build());
+ processBuilder.redirectInput(ProcessBuilder.Redirect.PIPE);
+ processBuilder.redirectOutput(ProcessBuilder.Redirect.PIPE);
+ processBuilder.redirectError(ProcessBuilder.Redirect.PIPE);
+ return processBuilder.start();
+ }
+
+ private static Thread launchLogger(InputStream input) {
+ BufferedReader reader = new BufferedReader(new InputStreamReader(input));
+ Thread logThread = new Thread(() -> {
+ try {
+ String line;
+ while ((line = reader.readLine()) != null) {
+ CLog.logAndDisplay(LogLevel.DEBUG, "cvd_test_gce_driver output: %s", line);
+ }
+ } catch (Exception e) {
+ CLog.logAndDisplay(LogLevel.DEBUG, "cvd_test_gce_driver exception: %s", e);
+ }
+ });
+ logThread.start();
+ return logThread;
+ }
+
+ private String createInstance() throws IOException {
+ String desiredName = "cuttlefish-integration-" + UUID.randomUUID();
+ TestMessage.Builder request = TestMessage.newBuilder();
+ request.getCreateInstanceBuilder().getIdBuilder().setName(desiredName);
+ request.getCreateInstanceBuilder().getIdBuilder().setZone(zone);
+ sendMessage(request.build());
+ ImmutableList<TestMessage> errors =
+ collectResponses().stream().filter(TestMessage::hasError).collect(toImmutableList());
+ if (errors.size() > 0) {
+ throw new IOException("Failed to create instance: " + errors);
+ }
+ return desiredName;
+ }
+
+ @AutoValue
+ public static abstract class SshResult {
+ public static SshResult create(File stdout, File stderr, int ret) {
+ return new AutoValue_GceInstanceRule_SshResult(stdout, stderr, ret);
+ }
+
+ public abstract File stdout();
+ public abstract File stderr();
+ public abstract int returnCode();
+ }
+
+ public SshResult ssh(String... command) throws IOException {
+ return ssh(ImmutableList.copyOf(command));
+ }
+
+ public SshResult ssh(ImmutableList<String> command) throws IOException {
+ TestMessage.Builder request = TestMessage.newBuilder();
+ request.getSshCommandBuilder().getInstanceBuilder().setName(managedInstance);
+ request.getSshCommandBuilder().addAllArguments(command);
+ sendMessage(request.build());
+ File stdout = FileUtil.createTempFile("ssh_", "_stdout.txt");
+ OutputStream stdoutStream = new FileOutputStream(stdout);
+ File stderr = FileUtil.createTempFile("ssh_", "_stderr.txt");
+ OutputStream stderrStream = new FileOutputStream(stderr);
+ int returnCode = -1;
+ IOException storedException = null;
+ while (true) {
+ TestMessage response = receiveMessage();
+ switch (response.getContentsCase()) {
+ case DATA:
+ if (response.getData().getType().equals(DataType.DATA_TYPE_STDOUT)) {
+ stdoutStream.write(response.getData().getContents().toByteArray());
+ } else if (response.getData().getType().equals(DataType.DATA_TYPE_STDERR)) {
+ stderrStream.write(response.getData().getContents().toByteArray());
+ } else if (response.getData().getType().equals(DataType.DATA_TYPE_RETURN_CODE)) {
+ returnCode = Integer.valueOf(response.getData().getContents().toStringUtf8());
+ } else {
+ throw new RuntimeException("Unexpected type: " + response.getData().getType());
+ }
+ break;
+ case ERROR:
+ if (storedException == null) {
+ storedException = new IOException(response.getError().getText());
+ } else {
+ storedException.addSuppressed(new IOException(response.getError().getText()));
+ }
+ case STREAM_END:
+ if (storedException == null) {
+ return SshResult.create(stdout, stderr, returnCode);
+ } else {
+ throw storedException;
+ }
+ default: {
+ IOException exception = new IOException("Unexpected message: " + response);
+ if (storedException == null) {
+ exception.addSuppressed(storedException);
+ }
+ throw exception;
+ }
+ }
+ }
+ }
+
+ public void uploadFile(File sourceFile, String destFile) throws IOException {
+ TestMessage.Builder request = TestMessage.newBuilder();
+ request.getUploadFileBuilder().getInstanceBuilder().setName(managedInstance);
+ request.getUploadFileBuilder().setRemotePath(destFile);
+
+ // Allow this to error out before initiating the transfer
+ FileInputStream stream = new FileInputStream(sourceFile);
+ sendMessage(request.build());
+ byte[] buffer = new byte[1 << 14 /* 16 KiB */];
+ int read = 0;
+ while ((read = stream.read(buffer)) != -1) {
+ TestMessage.Builder dataMessage = TestMessage.newBuilder();
+ dataMessage.getDataBuilder().setType(DataType.DATA_TYPE_FILE_CONTENTS);
+ dataMessage.getDataBuilder().setContents(ByteString.copyFrom(buffer, 0, read));
+ sendMessage(dataMessage.build());
+ }
+ TestMessage.Builder endRequest = TestMessage.newBuilder();
+ endRequest.setStreamEnd(StreamEnd.getDefaultInstance());
+ sendMessage(endRequest.build());
+ ImmutableList<TestMessage> errors =
+ collectResponses().stream().filter(TestMessage::hasError).collect(toImmutableList());
+ if (errors.size() > 0) {
+ throw new IOException("Failed to upload file: " + errors);
+ }
+ }
+
+ public void uploadBuildArtifact(String artifact, String destFile) throws IOException {
+ TestMessage.Builder request = TestMessage.newBuilder();
+ request.getUploadBuildArtifactBuilder().getInstanceBuilder().setName(managedInstance);
+ request.getUploadBuildArtifactBuilder().setBuild(buildChooser.buildProto());
+ request.getUploadBuildArtifactBuilder().setArtifactName(artifact);
+ request.getUploadBuildArtifactBuilder().setRemotePath(destFile);
+ sendMessage(request.build());
+ ImmutableList<TestMessage> errors =
+ collectResponses().stream().filter(TestMessage::hasError).collect(toImmutableList());
+ if (errors.size() > 0) {
+ throw new IOException("Failed to upload build artifact: " + errors);
+ }
+ }
+
+ private TestMessage receiveMessage() throws IOException {
+ TestMessage message = TestMessage.parser().parseDelimitedFrom(driverProcess.getInputStream());
+ CLog.logAndDisplay(LogLevel.DEBUG, "Received message \"" + message + "\"");
+ return message;
+ }
+
+ private ImmutableList<TestMessage> collectResponses() throws IOException {
+ ImmutableList.Builder<TestMessage> messages = ImmutableList.builder();
+ while (true) {
+ TestMessage received = receiveMessage();
+ messages.add(received);
+ if (received.hasStreamEnd()) {
+ return messages.build();
+ }
+ }
+ }
+
+ private void sendMessage(TestMessage message) throws IOException {
+ CLog.logAndDisplay(LogLevel.DEBUG, "Sending message \"" + message + "\"");
+ message.writeDelimitedTo(driverProcess.getOutputStream());
+ driverProcess.getOutputStream().flush();
+ }
+
+ @Override
+ public Statement apply(Statement base, Description description) {
+ final File gceDriver;
+ try {
+ gceDriver = testInfo.getDependencyFile("cvd_test_gce_driver", false);
+ } catch (FileNotFoundException e) {
+ assumeNoException("Could not find cvd_test_gce_driver", e);
+ return null;
+ }
+ assumeTrue("cvd_test_gce_driver file did not exist", gceDriver.exists());
+ return new Statement() {
+ @Override
+ public void evaluate() throws Throwable {
+ // TODO(schuffelen): Reuse instances with GCE resets.
+ // The trick will be figuring out when the instances can actually be destroyed.
+ driverProcess = launchDriver(gceDriver);
+ Thread logStderr = launchLogger(driverProcess.getErrorStream());
+ managedInstance = createInstance();
+ try {
+ base.evaluate();
+ } finally {
+ boolean cleanExit = false;
+ for (int i = 0; i < 10; i++) {
+ sendMessage(TestMessage.newBuilder().setExit(Exit.getDefaultInstance()).build());
+ TestMessage response = receiveMessage();
+ if (response.hasExit()) {
+ cleanExit = true;
+ break;
+ } else if (!response.hasError()
+ && !response.hasStreamEnd()) { // Swallow some errors to to get out if necessary
+ throw new AssertionError("Unexpected message " + response);
+ }
+ }
+ assertTrue("Failed to get an exit response", cleanExit);
+ assertEquals(0, driverProcess.waitFor());
+ logStderr.join();
+ driverProcess = null;
+ }
+ }
+ };
+ }
+}
diff --git a/tests/integration/com/android/cuttlefish/test/GceInstanceRuleTest.java b/tests/integration/com/android/cuttlefish/test/GceInstanceRuleTest.java
new file mode 100644
index 0000000..a21eab3
--- /dev/null
+++ b/tests/integration/com/android/cuttlefish/test/GceInstanceRuleTest.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.cuttlefish.test;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+
+import javax.inject.Inject;
+import org.junit.Assert;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(CuttlefishIntegrationTestRunner.class)
+public class GceInstanceRuleTest {
+ @Inject @Rule public GceInstanceRule gceInstance;
+
+ @Test
+ public void createInstance() {}
+
+ @Test
+ public void sshToInstance() throws Exception {
+ assertEquals(0, gceInstance.ssh("ls", "/").returnCode());
+ }
+
+ @Test
+ public void uploadBuildArtifact() throws Exception {
+ gceInstance.uploadBuildArtifact("fetch_cvd", "/home/vsoc-01/fetch_cvd");
+ assertEquals(0, gceInstance.ssh("chmod", "+x", "/home/vsoc-01/fetch_cvd").returnCode());
+ }
+}
diff --git a/tests/integration/com/android/cuttlefish/test/LaunchTest.java b/tests/integration/com/android/cuttlefish/test/LaunchTest.java
new file mode 100644
index 0000000..502e6cc
--- /dev/null
+++ b/tests/integration/com/android/cuttlefish/test/LaunchTest.java
@@ -0,0 +1,68 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.cuttlefish.test;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+
+import javax.inject.Inject;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(CuttlefishIntegrationTestRunner.class)
+public class LaunchTest {
+ @Inject @Rule public GceInstanceRule gceInstance;
+ @Inject private BuildChooser buildChooser;
+
+ @Before
+ public void downloadInstanceFiles() throws Exception {
+ gceInstance.uploadBuildArtifact("fetch_cvd", "fetch_cvd");
+ assertEquals(0, gceInstance.ssh("chmod", "+x", "fetch_cvd").returnCode());
+ // TODO(schuffelen): Make this fetch the current build
+ assertEquals(0,
+ gceInstance.ssh("./fetch_cvd", "-default_build=" + buildChooser.fetchCvdBuild())
+ .returnCode());
+ }
+
+ @Test
+ public void launchDaemon() throws Exception {
+ assertEquals(0,
+ gceInstance.ssh("bin/launch_cvd", "--daemon", "--report_anonymous_usage_stats=y")
+ .returnCode());
+ }
+
+ @Test
+ public void launchDaemonStop() throws Exception {
+ assertEquals(0,
+ gceInstance.ssh("bin/launch_cvd", "--daemon", "--report_anonymous_usage_stats=y")
+ .returnCode());
+ assertEquals(0, gceInstance.ssh("bin/stop_cvd").returnCode());
+ }
+
+ @Test
+ public void launchDaemonStopLaunch() throws Exception {
+ assertEquals(0,
+ gceInstance.ssh("bin/launch_cvd", "--daemon", "--report_anonymous_usage_stats=y")
+ .returnCode());
+ assertEquals(0, gceInstance.ssh("bin/stop_cvd").returnCode());
+ assertEquals(0,
+ gceInstance.ssh("bin/launch_cvd", "--daemon", "--report_anonymous_usage_stats=y")
+ .returnCode());
+ }
+}
diff --git a/tests/integration/com/android/cuttlefish/test/SetOption.java b/tests/integration/com/android/cuttlefish/test/SetOption.java
new file mode 100644
index 0000000..ede1854
--- /dev/null
+++ b/tests/integration/com/android/cuttlefish/test/SetOption.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.cuttlefish.test;
+
+import static java.lang.annotation.ElementType.FIELD;
+import static java.lang.annotation.ElementType.METHOD;
+import static java.lang.annotation.ElementType.PARAMETER;
+import static java.lang.annotation.RetentionPolicy.RUNTIME;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.Target;
+import javax.inject.Qualifier;
+
+/**
+ * Binding annotation for Tradefed options.
+ *
+ * If an option is given to tradefed in the form of
+ *
+ * <pre>
+ * --test-arg com.android.tradefed.testtype.HostTest:set-option:OPTION:VALUE
+ * </pre>
+ *
+ * {@link CuttlefishIntegrationTestRunner} will create a binding of {@code SetOption("OPTION")}
+ * and to a string with contents {@code VALUE}.
+ */
+@Qualifier
+@Target({FIELD, PARAMETER, METHOD})
+@Retention(RUNTIME)
+public @interface SetOption {
+ String value();
+}
diff --git a/tests/powerwash/src/com/android/cuttlefish/tests/PowerwashTest.java b/tests/powerwash/src/com/android/cuttlefish/tests/PowerwashTest.java
index 84fa852..42d9dda 100644
--- a/tests/powerwash/src/com/android/cuttlefish/tests/PowerwashTest.java
+++ b/tests/powerwash/src/com/android/cuttlefish/tests/PowerwashTest.java
@@ -15,15 +15,19 @@
*/
package com.android.cuttlefish.tests;
+import static org.junit.Assert.assertTrue;
+
import com.android.tradefed.device.cloud.RemoteAndroidVirtualDevice;
+import com.android.tradefed.device.internal.DeviceResetHandler;
import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
import com.android.tradefed.testtype.junit4.BaseHostJUnit4Test;
-import java.io.File;
import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;
+import java.io.File;
+
/**
* Test powerwash function.
*
@@ -48,14 +52,18 @@
if (file == null) {
Assert.fail("Setup failed: tmp file failed to persist after device reboot.");
}
-
+ boolean success = false;
if (getDevice() instanceof RemoteAndroidVirtualDevice) {
- ((RemoteAndroidVirtualDevice) getDevice()).powerwashGce();
+ success = ((RemoteAndroidVirtualDevice) getDevice()).powerwashGce();
} else {
- Assert.fail("This test only supports running in test lab setup.");
+ // We don't usually expect tests to use our feature server, but in this case we are
+ // validating the feature itself so it's fine
+ DeviceResetHandler handler = new DeviceResetHandler(getInvocationContext());
+ success = handler.resetDevice(getDevice());
}
+ assertTrue("Powerwash reset failed", success);
- // Verify that the device is back online and pre-xisting file is gone.
+ // Verify that the device is back online and pre-existing file is gone.
file = getDevice().pullFile(tmpFile);
if (file != null) {
Assert.fail("Powerwash failed: pre-existing file still exists.");
diff --git a/tests/ril/src/com/android/cuttlefish/ril/tests/RilE2eTests.java b/tests/ril/src/com/android/cuttlefish/ril/tests/RilE2eTests.java
index 3c5db3f..d633991 100644
--- a/tests/ril/src/com/android/cuttlefish/ril/tests/RilE2eTests.java
+++ b/tests/ril/src/com/android/cuttlefish/ril/tests/RilE2eTests.java
@@ -23,8 +23,8 @@
import android.net.NetworkInfo;
import android.net.wifi.WifiManager;
import android.os.Build;
-import android.telephony.CellInfoGsm;
-import android.telephony.CellSignalStrengthGsm;
+import android.telephony.CellInfoLte;
+import android.telephony.CellSignalStrengthLte;
import android.telephony.TelephonyManager;
import android.util.Log;
@@ -35,7 +35,6 @@
import org.junit.Assert;
import org.junit.Assume;
import org.junit.Before;
-import org.junit.Ignore;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
@@ -141,13 +140,11 @@
Assert.assertSame(TelephonyManager.DATA_CONNECTED, mTeleManager.getDataState());
}
- // See b/74256305
- @Ignore
@Test
public void testSignalLevels() throws Exception {
- CellInfoGsm cellinfogsm = (CellInfoGsm)mTeleManager.getAllCellInfo().get(0);
- CellSignalStrengthGsm cellSignalStrengthGsm = cellinfogsm.getCellSignalStrength();
- int bars = cellSignalStrengthGsm.getLevel();
+ CellInfoLte cellInfo = (CellInfoLte) mTeleManager.getAllCellInfo().get(0);
+ CellSignalStrengthLte signalStrength = cellInfo.getCellSignalStrength();
+ int bars = signalStrength.getLevel();
Assert.assertThat("Signal Bars", bars, greaterThan(1));
}
}
diff --git a/tools/Android.bp b/tools/Android.bp
new file mode 100644
index 0000000..fe771af
--- /dev/null
+++ b/tools/Android.bp
@@ -0,0 +1,8 @@
+package {
+ default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+blueprint_go_binary {
+ name: "create_base_image",
+ srcs: ["create_base_image.go"],
+}
diff --git a/tools/create_base_image.go b/tools/create_base_image.go
new file mode 100644
index 0000000..40d533b
--- /dev/null
+++ b/tools/create_base_image.go
@@ -0,0 +1,262 @@
+package main
+
+import (
+ "os"
+ "os/exec"
+ "os/user"
+ "flag"
+ "fmt"
+ "strings"
+ "io/ioutil"
+ "log"
+ "time"
+)
+
+type OnFail int
+
+const (
+ IgnoreOnFail OnFail = iota
+ WarnOnFail
+ ExitOnFail
+)
+
+var build_instance string
+var build_project string
+var build_zone string
+var dest_image string
+var dest_family string
+var dest_project string
+var launch_instance string
+var source_image_family string
+var source_image_project string
+var repository_url string
+var repository_branch string
+var version string
+var SSH_FLAGS string
+var INTERNAL_extra_source string
+var verbose bool
+var username string
+
+func init() {
+ user, err := user.Current()
+ if err != nil {
+ panic(err)
+ }
+ username = user.Username
+
+ flag.StringVar(&build_instance, "build_instance",
+ username+"-build", "Instance name to create for the build")
+ flag.StringVar(&build_project, "build_project",
+ mustShell("gcloud config get-value project"), "Project to use for scratch")
+ flag.StringVar(&build_zone, "build_zone",
+ mustShell("gcloud config get-value compute/zone"),
+ "Zone to use for scratch resources")
+ flag.StringVar(&dest_image, "dest_image",
+ "vsoc-host-scratch-"+username, "Image to create")
+ flag.StringVar(&dest_family, "dest_family", "",
+ "Image family to add the image to")
+ flag.StringVar(&dest_project, "dest_project",
+ mustShell("gcloud config get-value project"), "Project to use for the new image")
+ flag.StringVar(&launch_instance, "launch_instance", "",
+ "Name of the instance to launch with the new image")
+ flag.StringVar(&source_image_family, "source_image_family", "debian-11",
+ "Image familty to use as the base")
+ flag.StringVar(&source_image_project, "source_image_project", "debian-cloud",
+ "Project holding the base image")
+ flag.StringVar(&repository_url, "repository_url",
+ "https://github.com/google/android-cuttlefish.git",
+ "URL to the repository with host changes")
+ flag.StringVar(&repository_branch, "repository_branch",
+ "main", "Branch to check out")
+ flag.StringVar(&version, "version", "", "cuttlefish-common version")
+ flag.StringVar(&SSH_FLAGS, "INTERNAL_IP", "",
+ "INTERNAL_IP can be set to --internal-ip run on a GCE instance."+
+ "The instance will need --scope compute-rw.")
+ flag.StringVar(&INTERNAL_extra_source, "INTERNAL_extra_source", "",
+ "INTERNAL_extra_source may be set to a directory containing the source for extra packages to build.")
+ flag.BoolVar(&verbose, "verbose", true, "print commands and output (default: true)")
+ flag.Parse()
+}
+
+func shell(cmd string) (string, error) {
+ if verbose {
+ fmt.Println(cmd)
+ }
+ b, err := exec.Command("/bin/sh", "-c", cmd).CombinedOutput()
+ if verbose {
+ fmt.Println(string(b))
+ }
+ if err != nil {
+ return "", err
+ }
+ return strings.TrimSpace(string(b)), nil
+}
+
+func mustShell(cmd string) string {
+ if verbose {
+ fmt.Println(cmd)
+ }
+ out, err := shell(cmd)
+ if err != nil {
+ panic(err)
+ }
+ if verbose {
+ fmt.Println(out)
+ }
+ return strings.TrimSpace(out)
+}
+
+func gce(action OnFail, gceArg string, errorStr ...string) (string, error) {
+ cmd := "gcloud " + gceArg
+ out, err := shell(cmd)
+ if out != "" {
+ fmt.Println(out)
+ }
+ if err != nil && action != IgnoreOnFail {
+ var buf string
+ fmt.Sprintf(buf, "gcloud error occurred: %s", err)
+ if (len(errorStr) > 0) {
+ buf += " [" + errorStr[0] + "]"
+ }
+ if action == ExitOnFail {
+ panic(buf)
+ }
+ if action == WarnOnFail {
+ fmt.Println(buf)
+ }
+ }
+ return out, err
+}
+
+func waitForInstance(PZ string) {
+ for {
+ time.Sleep(5 * time.Second)
+ _, err := gce(WarnOnFail, `compute ssh `+SSH_FLAGS+` `+PZ+` `+
+ build_instance+` -- uptime`)
+ if err == nil {
+ break
+ }
+ }
+}
+
+func packageSource(url string, branch string, version string, subdir string) {
+ repository_dir := url[strings.LastIndex(url, "/")+1:]
+ debian_dir := mustShell(`basename "`+repository_dir+`" .git`)
+ if subdir != "" {
+ debian_dir = repository_dir + "/" + subdir
+ }
+ mustShell("git clone " + url + " -b "+branch)
+ mustShell("dpkg-source -b " + debian_dir)
+ mustShell("rm -rf " + debian_dir)
+ mustShell("ls -l")
+ mustShell("pwd")
+}
+
+func createInstance(instance string, arg string) {
+ _, err := gce(WarnOnFail, `compute instances describe "`+instance+`"`)
+ if err != nil {
+ gce(ExitOnFail, `compute instances create `+arg+` "`+instance+`"`)
+ }
+}
+
+func main() {
+ gpu_type := "nvidia-tesla-p100-vws"
+ PZ := "--project=" + build_project + " --zone=" + build_zone
+
+ dest_family_flag := ""
+ if dest_family != "" {
+ dest_family_flag = "--family=" + dest_family
+ }
+
+ scratch_dir, err := ioutil.TempDir("", "")
+ if err != nil {
+ log.Fatal(err)
+ }
+
+ oldDir, err := os.Getwd()
+ if err != nil {
+ log.Fatal(err)
+ }
+ os.Chdir(scratch_dir)
+ packageSource(repository_url, repository_branch, "cuttlefish-common_" + version, "")
+ os.Chdir(oldDir)
+
+ abt := os.Getenv("ANDROID_BUILD_TOP")
+ source_files := `"` + abt + `/device/google/cuttlefish/tools/create_base_image_gce.sh"`
+ source_files += " " + `"` + abt + `/device/google/cuttlefish/tools/update_gce_kernel.sh"`
+ source_files += " " + `"` + abt + `/device/google/cuttlefish/tools/remove_old_gce_kernel.sh"`
+ source_files += " " + scratch_dir + "/*"
+ if INTERNAL_extra_source != "" {
+ source_files += " " + INTERNAL_extra_source + "/*"
+ }
+
+ delete_instances := build_instance + " " + dest_image
+ if launch_instance != "" {
+ delete_instances += " " + launch_instance
+ }
+
+ gce(WarnOnFail, `compute instances delete -q `+PZ+` `+delete_instances,
+ `Not running`)
+ gce(WarnOnFail, `compute disks delete -q `+PZ+` "`+dest_image+
+ `"`, `No scratch disk`)
+ gce(WarnOnFail, `compute images delete -q --project="`+build_project+
+ `" "`+dest_image+`"`, `Not respinning`)
+ gce(WarnOnFail, `compute disks create `+PZ+` --image-family="`+source_image_family+
+ `" --image-project="`+source_image_project+`" "`+dest_image+`"`)
+ gce(ExitOnFail, `compute accelerator-types describe "`+gpu_type+`" `+PZ,
+ `Please use a zone with `+gpu_type+` GPUs available.`)
+ createInstance(build_instance, PZ+
+ ` --machine-type=n1-standard-16 --image-family="`+source_image_family+
+ `" --image-project="`+source_image_project+
+ `" --boot-disk-size=200GiB --accelerator="type=`+gpu_type+
+ `,count=1" --maintenance-policy=TERMINATE --boot-disk-size=200GiB`)
+
+ waitForInstance(PZ)
+
+ // Ubuntu tends to mount the wrong disk as root, so help it by waiting until
+ // it has booted before giving it access to the clean image disk
+ gce(WarnOnFail, `compute instances attach-disk `+PZ+` "`+build_instance+
+ `" --disk="`+dest_image+`"`)
+
+ // beta for the --internal-ip flag that may be passed via SSH_FLAGS
+ gce(ExitOnFail, `beta compute scp `+SSH_FLAGS+` `+PZ+` `+source_files+
+ ` "`+build_instance+`:"`)
+
+ // Update the host kernel before installing any kernel modules
+ // Needed to guarantee that the modules in the chroot aren't built for the
+ // wrong kernel
+ gce(WarnOnFail, `compute ssh `+SSH_FLAGS+` `+PZ+` "`+build_instance+
+ `" -- ./update_gce_kernel.sh`)
+ // TODO rammuthiah if the instance is clobbered with ssh commands within
+ // 5 seconds of reboot, it becomes inaccessible. Workaround that by sleeping
+ // 50 seconds.
+ time.Sleep(50 * time.Second)
+ gce(ExitOnFail, `compute ssh `+SSH_FLAGS+` `+PZ+` "`+build_instance+
+ `" -- ./remove_old_gce_kernel.sh`)
+
+ gce(ExitOnFail, `compute ssh `+SSH_FLAGS+` `+PZ+` "`+build_instance+
+ `" -- ./create_base_image_gce.sh`)
+ gce(ExitOnFail, `compute instances delete -q `+PZ+` "`+build_instance+`"`)
+ gce(ExitOnFail, `compute images create --project="`+build_project+
+ `" --source-disk="`+dest_image+`" --source-disk-zone="`+build_zone+
+ `" --licenses=https://www.googleapis.com/compute/v1/projects/vm-options/global/licenses/enable-vmx `+
+ dest_family_flag+` "`+dest_image+`"`)
+ gce(ExitOnFail, `compute disks delete -q `+PZ+` "`+dest_image+`"`)
+
+ if launch_instance != "" {
+ createInstance(launch_instance, PZ+
+ ` --image-project="`+build_project+`" --image="`+dest_image+
+ `" --machine-type=n1-standard-4 --scopes storage-ro --accelerator="type=`+
+ gpu_type+`,count=1" --maintenance-policy=TERMINATE`)
+ }
+
+ fmt.Printf("Test and if this looks good, consider releasing it via:\n"+
+ "\n"+
+ "gcloud compute images create \\\n"+
+ " --project=\"%s\" \\\n"+
+ " --source-image=\"%s\" \\\n"+
+ " --source-image-project=\"%s\" \\\n"+
+ " \"%s\" \\\n"+
+ " \"%s\"\n",
+ dest_project, dest_image, build_project, dest_family_flag, dest_image)
+}
diff --git a/tools/create_base_image.sh b/tools/create_base_image.sh
deleted file mode 100755
index bd85f0b..0000000
--- a/tools/create_base_image.sh
+++ /dev/null
@@ -1,8 +0,0 @@
-#!/bin/bash
-
-# Creates a base image suitable for booting cuttlefish on GCE
-
-source "${ANDROID_BUILD_TOP}/device/google/cuttlefish/tools/create_base_image_hostlib.sh"
-
-FLAGS "$@" || exit 1
-main "${FLAGS_ARGV[@]}"
diff --git a/tools/create_base_image_arm.sh b/tools/create_base_image_arm.sh
index 644d909..5258636 100755
--- a/tools/create_base_image_arm.sh
+++ b/tools/create_base_image_arm.sh
@@ -14,8 +14,16 @@
# See the License for the specific language governing permissions and
# limitations under the License.
+set -e
+set -u
+
script_dir="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )"
+if [ -z $ANDROID_BUILD_TOP ]; then
+ echo "error: run script after 'lunch'"
+ exit 1
+fi
+
source "${ANDROID_BUILD_TOP}/external/shflags/shflags"
DEFINE_boolean p1 \
@@ -29,7 +37,11 @@
DEFINE_boolean p5 \
false "Only generate/write the 5th partition (rootfs)" "5"
-FLAGS_HELP="USAGE: $0 <KERNEL_DIR> [IMAGE] [flags]"
+UBOOT_REPO=
+KERNEL_REPO=
+IMAGE=
+
+FLAGS_HELP="USAGE: $0 <UBOOT_REPO> <KERNEL_REPO> [IMAGE] [flags]"
FLAGS "$@" || exit $?
eval set -- "${FLAGS_ARGV}"
@@ -47,8 +59,10 @@
fi
for arg in "$@" ; do
- if [ -z $KERNEL_DIR ]; then
- KERNEL_DIR=$arg
+ if [ -z $UBOOT_REPO ]; then
+ UBOOT_REPO=$arg
+ elif [ -z $KERNEL_REPO ]; then
+ KERNEL_REPO=$arg
elif [ -z $IMAGE ]; then
IMAGE=$arg
else
@@ -59,25 +73,18 @@
USE_IMAGE=`[ -z "${IMAGE}" ] && echo "0" || echo "1"`
OVERWRITE=`[ -e "${IMAGE}" ] && echo "1" || echo "0"`
-if [ -z $KERNEL_DIR ]; then
+if [ -z $KERNEL_REPO -o -z $UBOOT_REPO ]; then
flags_help
exit 1
fi
-if [ ! -e "${KERNEL_DIR}" ]; then
- echo "error: can't find '${KERNEL_DIR}'. aborting..."
+if [ ! -e "${UBOOT_REPO}" ]; then
+ echo "error: can't find '${UBOOT_REPO}'. aborting..."
exit 1
fi
-
-# escalate to superuser
-if [ $UID -ne 0 ]; then
- cd ${ANDROID_BUILD_TOP}
- . ./build/envsetup.sh
- lunch ${TARGET_PRODUCT}-${TARGET_BUILD_VARIANT}
- mmma external/u-boot
- cd -
- exec sudo -E "${0}" ${@}
+if [ ! -e "${KERNEL_REPO}" ]; then
+ echo "error: can't find '${KERNEL_REPO}'. aborting..."
+ exit 1
fi
-
if [ $OVERWRITE -eq 1 ]; then
OVERWRITE_IMAGE=${IMAGE}
IMAGE=`mktemp`
@@ -118,15 +125,6 @@
echo "Detected device at /dev/${mmc_dev}"
fi
-if [ ${FLAGS_p1} -eq ${FLAGS_TRUE} ]; then
- cd ${ANDROID_BUILD_TOP}/external/arm-trusted-firmware
- CROSS_COMPILE=aarch64-linux-gnu- make PLAT=rk3399 DEBUG=0 ERROR_DEPRECATED=1 bl31
- export BL31="${ANDROID_BUILD_TOP}/external/arm-trusted-firmware/build/rk3399/release/bl31/bl31.elf"
- cd -
-fi
-
-cd ${ANDROID_BUILD_TOP}/external/u-boot
-
if [ ${FLAGS_p2} -eq ${FLAGS_TRUE} ]; then
tmpfile=`mktemp`
bootenv=`mktemp`
@@ -144,474 +142,62 @@
find_script=if test -e mmc ${devnum}:${distro_bootpart} /boot/boot.scr; then echo Found U-Boot script /boot/boot.scr; run run_scr; fi
run_scr=load mmc ${devnum}:${distro_bootpart} ${scriptaddr} /boot/boot.scr; source ${scriptaddr}
EOF
- echo "Sha=`${script_dir}/gen_sha.sh --kernel ${KERNEL_DIR}`" >> ${tmpfile}
- ${ANDROID_HOST_OUT}/bin/mkenvimage -s 32768 -o ${bootenv} - < ${tmpfile}
+ echo "Sha=`${script_dir}/gen_sha.sh --uboot ${UBOOT_REPO} --kernel ${KERNEL_REPO}`" >> ${tmpfile}
+ ${ANDROID_BUILD_TOP}/device/google/cuttlefish_prebuilts/uboot_tools/mkenvimage -s 32768 -o ${bootenv} - < ${tmpfile}
fi
if [ ${FLAGS_p1} -eq ${FLAGS_TRUE} ] || [ ${FLAGS_p3} -eq ${FLAGS_TRUE} ]; then
- make ARCH=arm CROSS_COMPILE=aarch64-linux-gnu- rock-pi-4-rk3399_defconfig
- if [ ${FLAGS_p1} -eq ${FLAGS_TRUE} ]; then
- make ARCH=arm CROSS_COMPILE=aarch64-linux-gnu- -j`nproc`
- fi
- if [ ${FLAGS_p3} -eq ${FLAGS_TRUE} ]; then
- make ARCH=arm CROSS_COMPILE=aarch64-linux-gnu- u-boot.itb
- fi
- if [ ${FLAGS_p1} -eq ${FLAGS_TRUE} ]; then
- idbloader=`mktemp`
- ${ANDROID_HOST_OUT}/bin/mkimage -n rk3399 -T rksd -d tpl/u-boot-tpl.bin ${idbloader}
- cat spl/u-boot-spl.bin >> ${idbloader}
- fi
+ cd ${UBOOT_REPO}
+ BUILD_CONFIG=u-boot/build.config.rockpi4 build/build.sh -j1
+ cd -
fi
-cd -
if [ ${FLAGS_p5} -eq ${FLAGS_TRUE} ]; then
- ${ANDROID_BUILD_TOP}/kernel/tests/net/test/build_rootfs.sh -a arm64 -s buster -n ${IMAGE}
+ cd ${KERNEL_REPO}
+ rm -rf out
+ BUILD_CONFIG=common/build.config.rockpi4 build/build.sh -j`nproc`
+ cd -
+
+ dist_dir=$(echo ${KERNEL_REPO}/out/android*/dist)
+ ${ANDROID_BUILD_TOP}/kernel/tests/net/test/build_rootfs.sh \
+ -a arm64 -s bullseye-rockpi -n ${IMAGE} -r ${IMAGE}.initrd -e \
+ -k ${dist_dir}/Image -i ${dist_dir}/initramfs.img \
+ -d ${dist_dir}/rk3399-rock-pi-4b.dtb:rockchip
if [ $? -ne 0 ]; then
echo "error: failed to build rootfs. exiting..."
exit 1
fi
+ rm -f ${IMAGE}.initrd
truncate -s +3G ${IMAGE}
e2fsck -f ${IMAGE}
resize2fs ${IMAGE}
- mntdir=`mktemp -d`
- mount ${IMAGE} ${mntdir}
- if [ $? != 0 ]; then
- echo "error: unable to mount ${IMAGE} ${mntdir}"
- exit 1
- fi
-
- cat > ${mntdir}/boot/boot.cmd << "EOF"
-setenv start_poe 'gpio set 150; gpio clear 146'
-run start_poe
-setenv bootcmd_dhcp '
-mw.b ${scriptaddr} 0 0x8000
-mmc dev 0 0
-mmc read ${scriptaddr} 0x1fc0 0x40
-env import -b ${scriptaddr} 0x8000
-mw.b ${scriptaddr} 0 0x8000
-if dhcp ${scriptaddr} manifest.txt; then
- setenv OldSha ${Sha}
- setenv Sha
- env import -t ${scriptaddr} 0x8000 ManifestVersion
- echo "Manifest version $ManifestVersion";
- if test "$ManifestVersion" = "1"; then
- run manifest1
- elif test "$ManifestVersion" = "2"; then
- run manifest2
- else
- run manifestX
- fi
-fi'
-setenv manifestX 'echo "***** ERROR: Unknown manifest version! *****";'
-setenv manifest1 '
-env import -t ${scriptaddr} 0x8000
-if test "$Sha" != "$OldSha"; then
- setenv serverip ${TftpServer}
- setenv loadaddr 0x00200000
- mmc dev 0 0;
- setenv file $TplSplImg; offset=0x40; size=0x1f80; run tftpget1; setenv TplSplImg
- setenv file $UbootItb; offset=0x4000; size=0x2000; run tftpget1; setenv UbootItb
- setenv file $TrustImg; offset=0x6000; size=0x2000; run tftpget1; setenv TrustImg
- setenv file $RootfsImg; offset=0x8000; size=0; run tftpget1; setenv RootfsImg
- setenv file $UbootEnv; offset=0x1fc0; size=0x40; run tftpget1; setenv UbootEnv
- mw.b ${scriptaddr} 0 0x8000
- env export -b ${scriptaddr} 0x8000
- mmc write ${scriptaddr} 0x1fc0 0x40
-else
- echo "Already have ${Sha}. Booting..."
-fi'
-setenv manifest2 '
-env import -t ${scriptaddr} 0x8000
-if test "$DFUethaddr" = "$ethaddr" || test "$DFUethaddr" = ""; then
- if test "$Sha" != "$OldSha"; then
- setenv serverip ${TftpServer}
- setenv loadaddr 0x00200000
- mmc dev 0 0;
- setenv file $TplSplImg; offset=0x40; size=0x1f80; run tftpget1; setenv TplSplImg
- setenv file $UbootItb; offset=0x4000; size=0x2000; run tftpget1; setenv UbootItb
- setenv file $TrustImg; offset=0x6000; size=0x2000; run tftpget1; setenv TrustImg
- setenv file $RootfsImg; offset=0x8000; size=0; run tftpget1; setenv RootfsImg
- setenv file $UbootEnv; offset=0x1fc0; size=0x40; run tftpget1; setenv UbootEnv
- mw.b ${scriptaddr} 0 0x8000
- env export -b ${scriptaddr} 0x8000
- mmc write ${scriptaddr} 0x1fc0 0x40
- else
- echo "Already have ${Sha}. Booting..."
- fi
-else
- echo "Update ${Sha} is not for me. Booting..."
-fi'
-setenv tftpget1 '
-if test "$file" != ""; then
- mw.b ${loadaddr} 0 0x400000
- tftp ${file}
- if test $? = 0; then
- setenv isGz 0 && setexpr isGz sub .*\\.gz\$ 1 ${file}
- if test $isGz = 1; then
- if test ${file} = ${UbootEnv}; then
- echo "** gzipped env unsupported **"
- else
- setexpr boffset ${offset} * 0x200
- gzwrite mmc 0 ${loadaddr} 0x${filesize} 100000 ${boffset} && echo Updated: ${file}
- fi
- elif test ${file} = ${UbootEnv}; then
- env import -b ${loadaddr} && echo Updated: ${file}
- else
- if test $size = 0; then
- setexpr x $filesize - 1
- setexpr x $x / 0x1000
- setexpr x $x + 1
- setexpr x $x * 0x1000
- setexpr x $x / 0x200
- size=0x${x}
- fi
- mmc write ${loadaddr} ${offset} ${size} && echo Updated: ${file}
- fi
- fi
- if test $? != 0; then
- echo ** UPDATE FAILED: ${file} **
- fi
-fi'
-if mmc dev 1 0; then; else
- run bootcmd_dhcp;
-fi
-load mmc ${devnum}:${distro_bootpart} 0x02080000 /boot/Image
-load mmc ${devnum}:${distro_bootpart} 0x04000000 /boot/uInitrd
-load mmc ${devnum}:${distro_bootpart} 0x01f00000 /boot/dtb/rockchip/rk3399-rock-pi-4.dtb
-setenv finduuid "part uuid mmc ${devnum}:${distro_bootpart} uuid"
-run finduuid
-setenv bootargs "earlycon=uart8250,mmio32,0xff1a0000 console=ttyS2,1500000n8 loglevel=7 root=PARTUUID=${uuid} rootwait rootfstype=ext4 sdhci.debug_quirks=0x20000000 of_devlink=0"
-booti 0x02080000 0x04000000 0x01f00000
-EOF
- ${ANDROID_HOST_OUT}/bin/mkimage \
- -C none -A arm -T script -d ${mntdir}/boot/boot.cmd ${mntdir}/boot/boot.scr
-
- cd ${KERNEL_DIR}
- export PATH=${ANDROID_BUILD_TOP}/prebuilts/clang/host/linux-x86/clang-r353983c/bin:$PATH
- export PATH=${ANDROID_BUILD_TOP}/prebuilts/gcc/linux-x86/aarch64/aarch64-linux-android-4.9/bin:$PATH
- make ARCH=arm64 CC=clang CROSS_COMPILE=aarch64-linux-androidkernel- \
- CLANG_TRIPLE=aarch64-linux-gnu- rockpi4_defconfig
- make ARCH=arm64 CC=clang CROSS_COMPILE=aarch64-linux-androidkernel- \
- CLANG_TRIPLE=aarch64-linux-gnu- -j`nproc`
-
- cp ${KERNEL_DIR}/arch/arm64/boot/Image ${mntdir}/boot/
- mkdir -p ${mntdir}/boot/dtb/rockchip/
- cp ${KERNEL_DIR}/arch/arm64/boot/dts/rockchip/rk3399-rock-pi-4.dtb ${mntdir}/boot/dtb/rockchip/
- cd -
-
- mount -o bind /proc ${mntdir}/proc
- mount -o bind /sys ${mntdir}/sys
- mount -o bind /dev ${mntdir}/dev
-
- echo "Installing required packages..."
- chroot ${mntdir} /bin/bash <<EOF
-apt-get update
-apt-get install -y -f initramfs-tools u-boot-tools network-manager openssh-server sudo man-db vim git dpkg-dev cdbs debhelper config-package-dev gdisk eject lzop binfmt-support ntpdate lsof
-EOF
-
- echo "Turning on DHCP client..."
- cat >${mntdir}/etc/systemd/network/dhcp.network <<EOF
-[Match]
-Name=en*
-
-[Network]
-DHCP=yes
-EOF
-
- chroot ${mntdir} /bin/bash << "EOT"
-echo "Adding user vsoc-01 and groups..."
-useradd -m -G kvm,sudo -d /home/vsoc-01 --shell /bin/bash vsoc-01
-echo -e "cuttlefish\ncuttlefish" | passwd
-echo -e "cuttlefish\ncuttlefish" | passwd vsoc-01
-EOT
-
- echo "Cloning android-cuttlefish..."
- cd ${mntdir}/home/vsoc-01
- git clone https://github.com/google/android-cuttlefish.git
- cd -
-
- echo "Creating PoE script..."
- cat > ${mntdir}/usr/local/bin/poe << "EOF"
-#!/bin/bash
-
-if [ "$1" == "--start" ]; then
- echo 146 > /sys/class/gpio/export
- echo out > /sys/class/gpio/gpio146/direction
- echo 0 > /sys/class/gpio/gpio146/value
- echo 150 > /sys/class/gpio/export
- echo out > /sys/class/gpio/gpio150/direction
- echo 1 > /sys/class/gpio/gpio150/value
- exit 0
-fi
-
-if [ "$1" == "--stop" ]; then
- echo 0 > /sys/class/gpio/gpio146/value
- echo 146 > /sys/class/gpio/unexport
- echo 0 > /sys/class/gpio/gpio150/value
- echo 150 > /sys/class/gpio/unexport
- exit 0
-fi
-
-if [ ! -e /sys/class/gpio/gpio146/value ] || [ ! -e /sys/class/gpio/gpio150/value ]; then
- echo "error: PoE service not initialized"
- exit 1
-fi
-
-if [ "$1" == "0" ] || [ "$1" == "off" ] || [ "$1" == "OFF" ]; then
- echo 0 > /sys/class/gpio/gpio150/value
- exit 0
-fi
-
-if [ "$1" == "1" ] || [ "$1" == "on" ] || [ "$1" == "ON" ]; then
- echo 1 > /sys/class/gpio/gpio150/value
- exit 0
-fi
-
-echo "usage: poe <0|1>"
-exit 1
-EOF
- chown root:root ${mntdir}/usr/local/bin/poe
- chmod 755 ${mntdir}/usr/local/bin/poe
-
- echo "Creating PoE service..."
- cat > ${mntdir}/etc/systemd/system/poe.service << EOF
-[Unit]
- Description=PoE service
- ConditionPathExists=/usr/local/bin/poe
-
-[Service]
- Type=oneshot
- ExecStart=/usr/local/bin/poe --start
- ExecStop=/usr/local/bin/poe --stop
- RemainAfterExit=true
- StandardOutput=journal
-
-[Install]
- WantedBy=multi-user.target
-EOF
-
- echo "Creating led script..."
- cat > ${mntdir}/usr/local/bin/led << "EOF"
-#!/bin/bash
-
-if [ "$1" == "--start" ]; then
- echo 125 > /sys/class/gpio/export
- echo out > /sys/class/gpio/gpio125/direction
- chmod 666 /sys/class/gpio/gpio125/value
- echo 0 > /sys/class/gpio/gpio125/value
- exit 0
-fi
-
-if [ "$1" == "--stop" ]; then
- echo 0 > /sys/class/gpio/gpio125/value
- echo 125 > /sys/class/gpio/unexport
- exit 0
-fi
-
-if [ ! -e /sys/class/gpio/gpio125/value ]; then
- echo "error: led service not initialized"
- exit 1
-fi
-
-if [ "$1" == "0" ] || [ "$1" == "off" ] || [ "$1" == "OFF" ]; then
- echo 0 > /sys/class/gpio/gpio125/value
- exit 0
-fi
-
-if [ "$1" == "1" ] || [ "$1" == "on" ] || [ "$1" == "ON" ]; then
- echo 1 > /sys/class/gpio/gpio125/value
- exit 0
-fi
-
-echo "usage: led <0|1>"
-exit 1
-EOF
- chown root:root ${mntdir}/usr/local/bin/led
- chmod 755 ${mntdir}/usr/local/bin/led
-
- echo "Creating led service..."
- cat > ${mntdir}/etc/systemd/system/led.service << EOF
-[Unit]
- Description=led service
- ConditionPathExists=/usr/local/bin/led
-
-[Service]
- Type=oneshot
- ExecStart=/usr/local/bin/led --start
- ExecStop=/usr/local/bin/led --stop
- RemainAfterExit=true
- StandardOutput=journal
-
-[Install]
- WantedBy=multi-user.target
-EOF
-
- echo "Creating SD duplicator script..."
- cat > ${mntdir}/usr/local/bin/sd-dupe << "EOF"
-#!/bin/bash
-led 0
-
-src_dev=mmcblk0
-dest_dev=mmcblk1
-part_num=p5
-
-if [ -e /dev/mmcblk0p5 ] && [ -e /dev/mmcblk1p5 ]; then
- led 1
-
- sgdisk -Z -a1 /dev/${dest_dev}
- sgdisk -a1 -n:1:64:8127 -t:1:8301 -c:1:loader1 /dev/${dest_dev}
- sgdisk -a1 -n:2:8128:8191 -t:2:8301 -c:2:env /dev/${dest_dev}
- sgdisk -a1 -n:3:16384:24575 -t:3:8301 -c:3:loader2 /dev/${dest_dev}
- sgdisk -a1 -n:4:24576:32767 -t:4:8301 -c:4:trust /dev/${dest_dev}
- sgdisk -a1 -n:5:32768:- -A:5:set:2 -t:5:8305 -c:5:rootfs /dev/${dest_dev}
-
- src_block_count=`tune2fs -l /dev/${src_dev}${part_num} | grep "Block count:" | sed 's/.*: *//'`
- src_block_size=`tune2fs -l /dev/${src_dev}${part_num} | grep "Block size:" | sed 's/.*: *//'`
- src_fs_size=$(( src_block_count*src_block_size ))
- src_fs_size_m=$(( src_fs_size / 1024 / 1024 + 1 ))
-
- dd if=/dev/${src_dev}p1 of=/dev/${dest_dev}p1 conv=sync,noerror status=progress
- dd if=/dev/${src_dev}p2 of=/dev/${dest_dev}p2 conv=sync,noerror status=progress
- dd if=/dev/${src_dev}p3 of=/dev/${dest_dev}p3 conv=sync,noerror status=progress
- dd if=/dev/${src_dev}p4 of=/dev/${dest_dev}p4 conv=sync,noerror status=progress
-
- echo "Writing ${src_fs_size_m} MB: /dev/${src_dev} -> /dev/${dest_dev}..."
- dd if=/dev/${src_dev}${part_num} of=/dev/${dest_dev}${part_num} bs=1M conv=sync,noerror status=progress
-
- echo "Expanding /dev/${dest_dev}${part_num} filesystem..."
- e2fsck -fy /dev/${dest_dev}${part_num}
- resize2fs /dev/${dest_dev}${part_num}
- tune2fs -O has_journal /dev/${dest_dev}${part_num}
- e2fsck -fy /dev/${dest_dev}${part_num}
- sync /dev/${dest_dev}
-
- echo "Cleaning up..."
- mount /dev/${dest_dev}${part_num} /media
- chroot /media /usr/local/bin/install-cleanup
-
- if [ $? == 0 ]; then
- echo "Successfully copied Rock Pi image!"
- while true; do
- led 1; sleep 0.5
- led 0; sleep 0.5
- done
- else
- echo "Error while copying Rock Pi image"
- while true; do
- led 1; sleep 0.1
- led 0; sleep 0.1
- done
- fi
-else
- echo "Expanding /dev/${dest_dev}${part_num} filesystem..."
- e2fsck -fy /dev/${dest_dev}${part_num}
- resize2fs /dev/${dest_dev}${part_num}
- tune2fs -O has_journal /dev/${dest_dev}${part_num}
- e2fsck -fy /dev/${dest_dev}${part_num}
- sync /dev/${dest_dev}
-
- echo "Cleaning up..."
- /usr/local/bin/install-cleanup
-fi
-EOF
- chmod +x ${mntdir}/usr/local/bin/sd-dupe
-
- echo "Creating SD duplicator service..."
- cat > ${mntdir}/etc/systemd/system/sd-dupe.service << EOF
-[Unit]
- Description=Duplicate SD card rootfs to eMMC on Rock Pi
- ConditionPathExists=/usr/local/bin/sd-dupe
- After=led.service
-
-[Service]
- Type=simple
- ExecStart=/usr/local/bin/sd-dupe
- TimeoutSec=0
- StandardOutput=tty
-
-[Install]
- WantedBy=multi-user.target
-EOF
-
- umount ${mntdir}/sys
- umount ${mntdir}/dev
- umount ${mntdir}/proc
-
- chroot ${mntdir} /bin/bash << "EOT"
-echo "Installing cuttlefish-common package..."
-dpkg --add-architecture amd64
-apt-get update
-apt-get install -y -f libc6:amd64 qemu-user-static
-cd /home/vsoc-01/android-cuttlefish
-dpkg-buildpackage -d -uc -us
-apt-get install -y -f ../cuttlefish-common_*_arm64.deb
-apt-get clean
-
-usermod -aG cvdnetwork vsoc-01
-chmod 660 /dev/vhost-vsock
-chown root:cvdnetwork /dev/vhost-vsock
-rm -rf /home/vsoc-01/*
-EOT
-
- echo "Creating cleanup script..."
- cat > ${mntdir}/usr/local/bin/install-cleanup << "EOF"
-#!/bin/bash
-echo "nameserver 8.8.8.8" > /etc/resolv.conf
-MAC=`ip link | grep eth0 -A1 | grep ether | sed 's/.*\(..:..:..:..:..:..\) .*/\1/' | tr -d :`
-sed -i " 1 s/.*/& rockpi-${MAC}/" /etc/hosts
-sudo hostnamectl set-hostname "rockpi-${MAC}"
-
-rm /etc/machine-id
-rm /var/lib/dbus/machine-id
-dbus-uuidgen --ensure
-systemd-machine-id-setup
-
-systemctl disable sd-dupe
-rm /etc/systemd/system/sd-dupe.service
-rm /usr/local/bin/sd-dupe
-rm /usr/local/bin/install-cleanup
-EOF
- chmod +x ${mntdir}/usr/local/bin/install-cleanup
-
- chroot ${mntdir} /bin/bash << "EOT"
-echo "Enabling services..."
-systemctl enable poe
-systemctl enable led
-systemctl enable sd-dupe
-
-echo "Creating Initial Ramdisk..."
-update-initramfs -c -t -k "5.2.0"
-mkimage -A arm -O linux -T ramdisk -C none -a 0 -e 0 -n uInitrd -d /boot/initrd.img-5.2.0 /boot/uInitrd-5.2.0
-ln -s /boot/uInitrd-5.2.0 /boot/uInitrd
-EOT
-
- umount ${mntdir}
-
# Turn on journaling
tune2fs -O ^has_journal ${IMAGE}
e2fsck -fy ${IMAGE} >/dev/null 2>&1
fi
if [ ${USE_IMAGE} -eq 0 ]; then
- # 32GB eMMC size
- end_sector=61071326
device=/dev/${mmc_dev}
devicep=${device}
- sgdisk -Z -a1 ${device}
- sgdisk -a1 -n:1:64:8127 -t:1:8301 -c:1:loader1 ${device}
- sgdisk -a1 -n:2:8128:8191 -t:2:8301 -c:2:env ${device}
- sgdisk -a1 -n:3:16384:24575 -t:3:8301 -c:3:loader2 ${device}
- sgdisk -a1 -n:4:24576:32767 -t:4:8301 -c:4:trust ${device}
- sgdisk -a1 -n:5:32768:${end_sector} -A:5:set:2 -t:5:8305 -c:5:rootfs ${device}
+ # 32GB eMMC size
+ end_sector=61071326
+
+ sudo sgdisk --zap-all --set-alignment=1 ${device}
+ sudo sgdisk --set-alignment=1 --new=1:64:8127 --typecode=1:8301 --change-name=1:loader1 ${device}
+ sudo sgdisk --set-alignment=1 --new=2:8128:8191 --typecode=2:8301 --change-name=2:env ${device}
+ sudo sgdisk --set-alignment=1 --new=3:16384:24575 --typecode=3:8301 --change-name=3:loader2 ${device}
+ sudo sgdisk --set-alignment=1 --new=4:24576:32767 --typecode=4:8301 --change-name=4:trust ${device}
+ sudo sgdisk --set-alignment=1 --new=5:32768:${end_sector} --typecode=5:8305 --change-name=5:rootfs --attributes=5:set:2 ${device}
if [ ${FLAGS_p5} -eq ${FLAGS_TRUE} ]; then
- dd if=${IMAGE} of=${devicep}5 bs=1M
- resize2fs ${devicep}5 >/dev/null 2>&1
+ sudo dd if=${IMAGE} of=${devicep}5 bs=1M conv=fsync
+ sudo resize2fs ${devicep}5 >/dev/null 2>&1
fi
else
- device=$(losetup -f)
+ device=$(sudo losetup -f)
devicep=${device}p
+
if [ ${FLAGS_p5} -eq ${FLAGS_FALSE} ]; then
fs_end=3G
end_sector=-
@@ -649,34 +235,37 @@
truncate -s ${fs_end} ${tmpimg}
# Create GPT
- sgdisk -Z -a1 ${tmpimg}
- sgdisk -a1 -n:1:64:8127 -t:1:8301 -c:1:loader1 ${tmpimg}
- sgdisk -a1 -n:2:8128:8191 -t:2:8301 -c:2:env ${tmpimg}
- sgdisk -a1 -n:3:16384:24575 -t:3:8301 -c:3:loader2 ${tmpimg}
- sgdisk -a1 -n:4:24576:32767 -t:4:8301 -c:4:trust ${tmpimg}
- sgdisk -a1 -n:5:32768:${end_sector} -A:5:set:2 -t:5:8305 -c:5:rootfs ${tmpimg}
+ sgdisk --zap-all --set-alignment=1 ${tmpimg}
+ sgdisk --set-alignment=1 --new=1:64:8127 --typecode=1:8301 --change-name=1:loader1 ${tmpimg}
+ sgdisk --set-alignment=1 --new=2:8128:8191 --typecode=2:8301 --change-name=2:env ${tmpimg}
+ sgdisk --set-alignment=1 --new=3:16384:24575 --typecode=3:8301 --change-name=3:loader2 ${tmpimg}
+ sgdisk --set-alignment=1 --new=4:24576:32767 --typecode=4:8301 --change-name=4:trust ${tmpimg}
+ sgdisk --set-alignment=1 --new=5:32768:${end_sector} --typecode=5:8305 --change-name=5:rootfs --attributes=5:set:2 ${tmpimg}
- losetup ${device} ${tmpimg}
- partx -v --add ${device}
+ sudo losetup ${device} ${tmpimg}
+ sudo partx -v --add ${device}
if [ ${FLAGS_p5} -eq ${FLAGS_TRUE} ]; then
- dd if=${IMAGE} of=${devicep}5 bs=1M
+ sudo dd if=${IMAGE} of=${devicep}5 bs=1M conv=fsync
fi
fi
if [ ${FLAGS_p1} -eq ${FLAGS_TRUE} ]; then
- dd if=${idbloader} of=${devicep}1
+ # sudo dd if=${UBOOT_REPO}/out/u-boot-mainline/dist/idbloader.img of=${devicep}1 conv=fsync
+ # loader1
+ sudo dd if=${ANDROID_BUILD_TOP}/device/google/cuttlefish_prebuilts/uboot_bin/idbloader.img of=${devicep}1 conv=fsync
fi
if [ ${FLAGS_p2} -eq ${FLAGS_TRUE} ]; then
- dd if=${bootenv} of=${devicep}2
+ sudo dd if=${bootenv} of=${devicep}2 conv=fsync
fi
if [ ${FLAGS_p3} -eq ${FLAGS_TRUE} ]; then
- dd if=${ANDROID_BUILD_TOP}/external/u-boot/u-boot.itb of=${devicep}3
+ # sudo dd if=${UBOOT_REPO}/out/u-boot-mainline/dist/u-boot.itb of=${devicep}3 conv=fsync
+ # loader2
+ sudo dd if=${ANDROID_BUILD_TOP}/device/google/cuttlefish_prebuilts/uboot_bin/u-boot.itb of=${devicep}3 conv=fsync
fi
if [ ${USE_IMAGE} -eq 1 ]; then
- chown $SUDO_USER:`id -ng $SUDO_USER` ${tmpimg}
+ sudo partx -v --delete ${device}
+ sudo losetup -d ${device}
if [ $OVERWRITE -eq 0 ]; then
mv ${tmpimg} ${IMAGE}
fi
- partx -v --delete ${device}
- losetup -d ${device}
fi
diff --git a/tools/create_base_image_gce.sh b/tools/create_base_image_gce.sh
index 02797db..8a3014d 100755
--- a/tools/create_base_image_gce.sh
+++ b/tools/create_base_image_gce.sh
@@ -39,7 +39,7 @@
done
# Now install the packages on the disk
-sudo mkdir /mnt/image
+sudo mkdir -p /mnt/image
sudo mount /dev/sdb1 /mnt/image
cp "${debs[@]}" /mnt/image/tmp
sudo mount -t sysfs none /mnt/image/sys
@@ -59,31 +59,30 @@
sudo chroot /mnt/image /usr/bin/apt install -y screen # needed by tradefed
sudo chroot /mnt/image /usr/bin/find /home -ls
+sudo chroot /mnt/image /usr/bin/apt install -t bullseye-backports -y linux-image-cloud-amd64
+sudo chroot /mnt/image /usr/bin/apt --purge -y remove linux-image-5.10.0-10-cloud-amd64
+# update QEMU version to most recent backport
+sudo chroot /mnt/image /usr/bin/apt install -y --only-upgrade qemu-system-x86 -t bullseye-backports
+sudo chroot /mnt/image /usr/bin/apt install -y --only-upgrade qemu-system-arm -t bullseye-backports
# Install GPU driver dependencies
sudo chroot /mnt/image /usr/bin/apt install -y gcc
sudo chroot /mnt/image /usr/bin/apt install -y linux-source
sudo chroot /mnt/image /usr/bin/apt install -y linux-headers-`uname -r`
sudo chroot /mnt/image /usr/bin/apt install -y make
+sudo chroot /mnt/image /usr/bin/apt install -y software-properties-common
+sudo chroot /mnt/image /usr/bin/add-apt-repository non-free
+sudo chroot /mnt/image /usr/bin/add-apt-repository contrib
+# TODO rammuthiah rootcause why this line is needed
+# For reasons unknown the above two lines don't add non-free and
+# contrib to the bullseye backports.
+sudo chroot /mnt/image /usr/bin/add-apt-repository 'deb http://deb.debian.org/debian bullseye-backports main non-free contrib'
+sudo chroot /mnt/image /usr/bin/apt update
-# Download the latest GPU driver installer
-gsutil cp \
- $(gsutil ls gs://nvidia-drivers-us-public/GRID/GRID*/*-Linux-x86_64-*.run \
- | sort \
- | tail -n 1) \
- /mnt/image/tmp/nvidia-driver-installer.run
-
-# Make GPU driver installer executable
-chmod +x /mnt/image/tmp/nvidia-driver-installer.run
-
-# Install the latest GPU driver with default options and the dispatch libs
-sudo chroot /mnt/image /tmp/nvidia-driver-installer.run \
- --silent \
- --install-libglvnd
-
-# Cleanup after install
-rm /mnt/image/tmp/nvidia-driver-installer.run
+sudo chroot /mnt/image /bin/bash -c 'DEBIAN_FRONTEND=noninteractive /usr/bin/apt install -y nvidia-driver -t bullseye-backports'
+sudo chroot /mnt/image /usr/bin/apt install -y firmware-misc-nonfree -t bullseye-backports
+sudo chroot /mnt/image /usr/bin/apt install -y libglvnd-dev -t bullseye-backports
# Verify
query_nvidia() {
@@ -101,11 +100,11 @@
fi
# Vulkan loader
-sudo chroot /mnt/image /usr/bin/apt install -y libvulkan1
+sudo chroot /mnt/image /usr/bin/apt install -y libvulkan1 -t bullseye-backports
# Wayland-server needed to have Nvidia driver fail gracefully when attemping to
# use the EGL API on GCE instances without a GPU.
-sudo chroot /mnt/image /usr/bin/apt install -y libwayland-server0
+sudo chroot /mnt/image /usr/bin/apt install -y libwayland-server0 -t bullseye-backports
# Clean up the builder's version of resolv.conf
sudo rm /mnt/image/etc/resolv.conf
diff --git a/tools/create_base_image_hostlib.sh b/tools/create_base_image_hostlib.sh
index dafefbd..cc7227f 100755
--- a/tools/create_base_image_hostlib.sh
+++ b/tools/create_base_image_hostlib.sh
@@ -2,9 +2,6 @@
# Common code to build a host image on GCE
-# INTERNAL_extra_source may be set to a directory containing the source for
-# extra package to build.
-
# INTERNAL_IP can be set to --internal-ip run on a GCE instance
# The instance will need --scope compute-rw
@@ -22,7 +19,7 @@
"Project to use for the new image" "p"
DEFINE_string launch_instance "" \
"Name of the instance to launch with the new image" "l"
-DEFINE_string source_image_family "debian-10" \
+DEFINE_string source_image_family "debian-11" \
"Image familty to use as the base" "s"
DEFINE_string source_image_project debian-cloud \
"Project holding the base image" "m"
@@ -79,9 +76,6 @@
"${ANDROID_BUILD_TOP}/device/google/cuttlefish/tools/create_base_image_gce.sh"
${scratch_dir}/*
)
- if [[ -n "${INTERNAL_extra_source}" ]]; then
- source_files+=("${INTERNAL_extra_source}"/*)
- fi
delete_instances=("${FLAGS_build_instance}" "${FLAGS_dest_image}")
if [[ -n "${FLAGS_launch_instance}" ]]; then
@@ -143,6 +137,7 @@
--scopes storage-ro \
--accelerator="type=${gpu_type},count=1" \
--maintenance-policy=TERMINATE \
+ --boot-disk-size=200GiB \
"${FLAGS_launch_instance}"
fi
cat <<EOF
@@ -156,3 +151,11 @@
"${FLAGS_dest_image}"
EOF
}
+
+FLAGS "$@" || exit 1
+if [[ "${FLAGS_help}" -eq 0 ]]; then
+ echo ${FLAGS_help}
+ exit 1
+fi
+
+main "${FLAGS_ARGV[@]}"
diff --git a/tools/gen_sha.sh b/tools/gen_sha.sh
index 51dc76c..d3da1c4 100755
--- a/tools/gen_sha.sh
+++ b/tools/gen_sha.sh
@@ -14,17 +14,22 @@
# See the License for the specific language governing permissions and
# limitations under the License.
-source "${ANDROID_BUILD_TOP}/external/shflags/src/shflags"
+set -e
+set -u
+
+source "${ANDROID_BUILD_TOP}/external/shflags/shflags"
DEFINE_string kernel \
- "" "Path to kernel build dir" "k"
+ "" "Path to kernel repo checkout" "k"
+DEFINE_string uboot \
+ "" "Path to u-boot repo checkout" "u"
FLAGS_HELP="USAGE: $0 [flags]"
FLAGS "$@" || exit $?
eval set -- "${FLAGS_ARGV}"
-if [ -z ${FLAGS_kernel} ]; then
+if [ -z ${FLAGS_kernel} -o -z ${FLAGS_uboot} ]; then
flags_help
exit 1
fi
@@ -32,13 +37,14 @@
cd "${ANDROID_BUILD_TOP}/device/google/cuttlefish"
Sha=`git rev-parse HEAD`
cd - >/dev/null
-cd "${ANDROID_BUILD_TOP}/external/u-boot"
+# cd "${FLAGS_uboot}/u-boot"
+cd "${ANDROID_BUILD_TOP}/device/google/cuttlefish_prebuilts"
Sha="$Sha,`git rev-parse HEAD`"
cd - >/dev/null
-cd "${ANDROID_BUILD_TOP}/external/arm-trusted-firmware"
+cd "${FLAGS_uboot}/external/arm-trusted-firmware"
Sha="$Sha,`git rev-parse HEAD`"
cd - >/dev/null
-cd "${FLAGS_kernel}"
+cd "${FLAGS_kernel}/common"
Sha="$Sha,`git rev-parse HEAD`"
cd - >/dev/null
echo $Sha
diff --git a/tools/go.mod b/tools/go.mod
new file mode 100644
index 0000000..00809b9
--- /dev/null
+++ b/tools/go.mod
@@ -0,0 +1,3 @@
+module tools
+
+go 1.17
diff --git a/tools/latest_fetch_cvd.sh b/tools/latest_fetch_cvd.sh
new file mode 100755
index 0000000..d853786
--- /dev/null
+++ b/tools/latest_fetch_cvd.sh
@@ -0,0 +1,14 @@
+#!/bin/bash
+
+LATEST_BUILD_ID=`curl "https://www.googleapis.com/android/internal/build/v3/builds?branch=aosp-master&buildAttemptStatus=complete&buildType=submitted&maxResults=1&successful=true&target=aosp_cf_x86_64_phone-userdebug" 2>/dev/null | \
+ python3 -c "import sys, json; print(json.load(sys.stdin)['builds'][0]['buildId'])"`
+LATEST_BUILD_URL=`curl "https://www.googleapis.com/android/internal/build/v3/builds/$LATEST_BUILD_ID/aosp_cf_x86_64_phone-userdebug/attempts/latest/artifacts/fetch_cvd/url" 2>/dev/null | \
+ python3 -c "import sys, json; print(json.load(sys.stdin)['signedUrl'])"`
+
+DOWNLOAD_TARGET=`mktemp`
+
+curl "${LATEST_BUILD_URL}" -o $DOWNLOAD_TARGET
+
+chmod +x $DOWNLOAD_TARGET
+
+exec $DOWNLOAD_TARGET $@
diff --git a/tools/remove_old_gce_kernel.sh b/tools/remove_old_gce_kernel.sh
new file mode 100755
index 0000000..c7d52a1
--- /dev/null
+++ b/tools/remove_old_gce_kernel.sh
@@ -0,0 +1,7 @@
+#!/bin/bash
+
+set -x
+set -o errexit
+
+sudo apt --purge -y remove linux-image-5.10.0-10-cloud-amd64
+sudo update-grub2
diff --git a/tools/update_gce_kernel.sh b/tools/update_gce_kernel.sh
new file mode 100755
index 0000000..d77cc3c
--- /dev/null
+++ b/tools/update_gce_kernel.sh
@@ -0,0 +1,7 @@
+#!/bin/bash
+
+set -x
+set -o errexit
+
+sudo apt install -t bullseye-backports -y linux-image-cloud-amd64
+sudo reboot
diff --git a/tools/upload_to_gce_and_run.py b/tools/upload_to_gce_and_run.py
index f4dc4b8..db78340 100755
--- a/tools/upload_to_gce_and_run.py
+++ b/tools/upload_to_gce_and_run.py
@@ -59,9 +59,6 @@
def __get_default_hostdir():
- soong_host_dir = os.environ.get('ANDROID_SOONG_HOST_OUT')
- if soong_host_dir:
- return soong_host_dir
return os.environ.get('ANDROID_HOST_OUT', '.')
diff --git a/tools/upload_via_ssh.py b/tools/upload_via_ssh.py
index 5359473..2b5cfd1 100755
--- a/tools/upload_via_ssh.py
+++ b/tools/upload_via_ssh.py
@@ -58,7 +58,7 @@
parser.add_argument(
'-host_dir',
type=str,
- default=os.environ.get('ANDROID_SOONG_HOST_OUT', '.'),
+ default=os.environ.get('ANDROID_HOST_OUT', '.'),
help='path to soong host out directory')
parser.add_argument(
'-image_dir',
diff --git a/vsoc_arm64/auto/aosp_cf.mk b/vsoc_arm64/auto/aosp_cf.mk
deleted file mode 100644
index 51ae7a3..0000000
--- a/vsoc_arm64/auto/aosp_cf.mk
+++ /dev/null
@@ -1,28 +0,0 @@
-#
-# Copyright (C) 2020 The Android Open Source Project
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-#
-
-$(call inherit-product, $(SRC_TARGET_DIR)/product/core_64_bit_only.mk)
-$(call inherit-product, device/google/cuttlefish/shared/auto/device.mk)
-$(call inherit-product, device/google/cuttlefish/vsoc_arm64/kernel.mk)
-
-PRODUCT_NAME := aosp_cf_arm64_auto
-PRODUCT_DEVICE := vsoc_arm64
-PRODUCT_MANUFACTURER := Google
-PRODUCT_MODEL := Cuttlefish arm64 auto
-
-PRODUCT_VENDOR_PROPERTIES += \
- ro.soc.manufacturer=$(PRODUCT_MANUFACTURER) \
- ro.soc.model=$(PRODUCT_DEVICE)
diff --git a/vsoc_arm64/bootloader.mk b/vsoc_arm64/bootloader.mk
index a5aea94..ce29443 100644
--- a/vsoc_arm64/bootloader.mk
+++ b/vsoc_arm64/bootloader.mk
@@ -18,5 +18,3 @@
# FIXME: Copying the QEMU bootloader for now, but this should be updated..
BOARD_PREBUILT_BOOTLOADER := \
device/google/cuttlefish_prebuilts/bootloader/crosvm_aarch64/u-boot.bin
-PRODUCT_COPY_FILES += \
- device/google/cuttlefish_prebuilts/bootloader/qemu_aarch64/u-boot.bin:bootloader.qemu
diff --git a/vsoc_arm64/kernel.mk b/vsoc_arm64/kernel.mk
index 19d1b7c..dca198b 100644
--- a/vsoc_arm64/kernel.mk
+++ b/vsoc_arm64/kernel.mk
@@ -13,7 +13,7 @@
# See the License for the specific language governing permissions and
# limitations under the License.
-TARGET_KERNEL_USE ?= 5.10
+TARGET_KERNEL_USE ?= 5.15
TARGET_KERNEL_PATH ?= kernel/prebuilts/$(TARGET_KERNEL_USE)/arm64/kernel-$(TARGET_KERNEL_USE)
PRODUCT_COPY_FILES += $(TARGET_KERNEL_PATH):kernel
diff --git a/vsoc_arm64/phone/aosp_cf.mk b/vsoc_arm64/phone/aosp_cf.mk
index b33e523..86fe29e 100644
--- a/vsoc_arm64/phone/aosp_cf.mk
+++ b/vsoc_arm64/phone/aosp_cf.mk
@@ -37,6 +37,8 @@
# All components inherited here go to vendor image
#
$(call inherit-product, device/google/cuttlefish/shared/phone/device_vendor.mk)
+# TODO(b/205788876) remove this when openwrt has an image for arm.
+PRODUCT_ENFORCE_MAC80211_HWSIM := false
# Nested virtualization support
$(call inherit-product, packages/modules/Virtualization/apex/product_packages.mk)
diff --git a/vsoc_x86_noapex/BoardConfig.mk b/vsoc_arm64/phone/aosp_cf_hwasan.mk
similarity index 61%
copy from vsoc_x86_noapex/BoardConfig.mk
copy to vsoc_arm64/phone/aosp_cf_hwasan.mk
index 934b3e9..8e19670 100644
--- a/vsoc_x86_noapex/BoardConfig.mk
+++ b/vsoc_arm64/phone/aosp_cf_hwasan.mk
@@ -1,5 +1,5 @@
#
-# Copyright 2019 The Android Open-Source Project
+# Copyright (C) 2021 The Android Open Source Project
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
@@ -14,10 +14,11 @@
# limitations under the License.
#
-#
-# x86 target for Cuttlefish that doesn't support APEX.
-#
+$(call inherit-product, device/google/cuttlefish/vsoc_arm64/phone/aosp_cf.mk)
-include device/google/cuttlefish/vsoc_x86/BoardConfig.mk
+PRODUCT_NAME := aosp_cf_arm64_phone_hwasan
-TARGET_FLATTEN_APEX := true
+# Add "hwaddress" as a global sanitizer if it's missing.
+ifeq ($(filter hwaddress,$(SANITIZE_TARGET)),)
+ SANITIZE_TARGET := $(strip $(SANITIZE_TARGET) hwaddress)
+endif
diff --git a/vsoc_arm64_only/BoardConfig.mk b/vsoc_arm64_only/BoardConfig.mk
index b5a1340..eede683 100644
--- a/vsoc_arm64_only/BoardConfig.mk
+++ b/vsoc_arm64_only/BoardConfig.mk
@@ -27,7 +27,7 @@
TARGET_CPU_VARIANT := cortex-a53
AUDIOSERVER_MULTILIB := first
-BOARD_VENDOR_RAMDISK_KERNEL_MODULES += $(wildcard kernel/prebuilts/common-modules/virtual-device/5.10/arm64/*.ko)
+BOARD_VENDOR_RAMDISK_KERNEL_MODULES += $(wildcard kernel/prebuilts/common-modules/virtual-device/$(TARGET_KERNEL_USE)/arm64/*.ko)
HOST_CROSS_OS := linux_bionic
HOST_CROSS_ARCH := arm64
diff --git a/vsoc_arm64/auto/OWNERS b/vsoc_arm64_only/auto/OWNERS
similarity index 100%
rename from vsoc_arm64/auto/OWNERS
rename to vsoc_arm64_only/auto/OWNERS
diff --git a/vsoc_arm64_only/auto/aosp_cf.mk b/vsoc_arm64_only/auto/aosp_cf.mk
new file mode 100644
index 0000000..33a8624
--- /dev/null
+++ b/vsoc_arm64_only/auto/aosp_cf.mk
@@ -0,0 +1,66 @@
+#
+# Copyright (C) 2022 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+#
+# All components inherited here go to system image
+#
+$(call inherit-product, $(SRC_TARGET_DIR)/product/core_64_bit_only.mk)
+$(call inherit-product, $(SRC_TARGET_DIR)/product/generic_system.mk)
+
+# FIXME: generic_system.mk sets 'PRODUCT_ENFORCE_RRO_TARGETS := *'
+# but this breaks phone_car. So undo it here.
+PRODUCT_ENFORCE_RRO_TARGETS := frameworks-res
+
+# FIXME: Disable mainline path checks
+PRODUCT_ENFORCE_ARTIFACT_PATH_REQUIREMENTS := false
+
+#
+# All components inherited here go to system_ext image
+#
+$(call inherit-product, $(SRC_TARGET_DIR)/product/base_system_ext.mk)
+
+#
+# All components inherited here go to product image
+#
+$(call inherit-product, $(SRC_TARGET_DIR)/product/aosp_product.mk)
+
+#
+# All components inherited here go to vendor image
+#
+LOCAL_DISABLE_OMX := true
+$(call inherit-product, device/google/cuttlefish/shared/auto/device_vendor.mk)
+
+# TODO(b/205788876) remove this when openwrt has an image for arm.
+PRODUCT_ENFORCE_MAC80211_HWSIM := false
+
+#
+# Special settings for the target
+#
+$(call inherit-product, device/google/cuttlefish/vsoc_arm64/kernel.mk)
+$(call inherit-product, device/google/cuttlefish/vsoc_arm64/bootloader.mk)
+
+# Exclude features that are not available on AOSP devices.
+PRODUCT_COPY_FILES += \
+ frameworks/native/data/etc/aosp_excluded_hardware.xml:$(TARGET_COPY_OUT_VENDOR)/etc/permissions/aosp_excluded_hardware.xml
+
+PRODUCT_NAME := aosp_cf_arm64_auto
+PRODUCT_DEVICE := vsoc_arm64_only
+PRODUCT_MANUFACTURER := Google
+PRODUCT_MODEL := Cuttlefish arm64 auto
+
+PRODUCT_VENDOR_PROPERTIES += \
+ ro.soc.manufacturer=$(PRODUCT_MANUFACTURER) \
+ ro.soc.model=$(PRODUCT_DEVICE)
diff --git a/vsoc_arm64_only/phone/aosp_cf.mk b/vsoc_arm64_only/phone/aosp_cf.mk
index 5bcfc7b..0da151f 100644
--- a/vsoc_arm64_only/phone/aosp_cf.mk
+++ b/vsoc_arm64_only/phone/aosp_cf.mk
@@ -39,6 +39,9 @@
LOCAL_DISABLE_OMX := true
$(call inherit-product, device/google/cuttlefish/shared/phone/device_vendor.mk)
+# TODO(b/205788876) remove this when openwrt has an image for arm.
+PRODUCT_ENFORCE_MAC80211_HWSIM := false
+
# Nested virtualization support
$(call inherit-product, packages/modules/Virtualization/apex/product_packages.mk)
diff --git a/vsoc_x86_noapex/BoardConfig.mk b/vsoc_arm64_only/phone/aosp_cf_hwasan.mk
similarity index 60%
copy from vsoc_x86_noapex/BoardConfig.mk
copy to vsoc_arm64_only/phone/aosp_cf_hwasan.mk
index 934b3e9..c261662 100644
--- a/vsoc_x86_noapex/BoardConfig.mk
+++ b/vsoc_arm64_only/phone/aosp_cf_hwasan.mk
@@ -1,5 +1,5 @@
#
-# Copyright 2019 The Android Open-Source Project
+# Copyright (C) 2021 The Android Open Source Project
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
@@ -14,10 +14,11 @@
# limitations under the License.
#
-#
-# x86 target for Cuttlefish that doesn't support APEX.
-#
+$(call inherit-product, device/google/cuttlefish/vsoc_arm64_only/phone/aosp_cf.mk)
-include device/google/cuttlefish/vsoc_x86/BoardConfig.mk
+PRODUCT_NAME := aosp_cf_arm64_only_phone_hwasan
-TARGET_FLATTEN_APEX := true
+# Add "hwaddress" as a global sanitizer if it's missing.
+ifeq ($(filter hwaddress,$(SANITIZE_TARGET)),)
+ SANITIZE_TARGET := $(strip $(SANITIZE_TARGET) hwaddress)
+endif
diff --git a/vsoc_arm64_only/slim/aosp_cf.mk b/vsoc_arm64_only/slim/aosp_cf.mk
new file mode 100644
index 0000000..8699275
--- /dev/null
+++ b/vsoc_arm64_only/slim/aosp_cf.mk
@@ -0,0 +1,68 @@
+#
+# Copyright (C) 2022 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+#
+# All components inherited here go to system image
+#
+$(call inherit-product, $(SRC_TARGET_DIR)/product/core_64_bit_only.mk)
+$(call inherit-product, $(SRC_TARGET_DIR)/product/generic_system.mk)
+
+PRODUCT_ENFORCE_ARTIFACT_PATH_REQUIREMENTS := relaxed
+
+#
+# All components inherited here go to system_ext image
+#
+$(call inherit-product, $(SRC_TARGET_DIR)/product/media_system_ext.mk)
+$(call inherit-product, $(SRC_TARGET_DIR)/product/telephony_system_ext.mk)
+
+#
+# All components inherited here go to product image
+#
+$(call inherit-product, $(SRC_TARGET_DIR)/product/media_product.mk)
+PRODUCT_PACKAGES += FakeSystemApp
+
+#
+# All components inherited here go to vendor image
+#
+LOCAL_DISABLE_OMX := true
+LOCAL_PREFER_VENDOR_APEX := true
+$(call inherit-product, device/google/cuttlefish/shared/slim/device_vendor.mk)
+
+# TODO(b/205788876) remove this when openwrt has an image for arm.
+PRODUCT_ENFORCE_MAC80211_HWSIM := false
+
+#
+# Special settings for the target
+#
+$(call inherit-product, device/google/cuttlefish/vsoc_arm64/kernel.mk)
+$(call inherit-product, device/google/cuttlefish/vsoc_arm64/bootloader.mk)
+
+# Exclude features that are not available on AOSP devices.
+ifeq ($(LOCAL_PREFER_VENDOR_APEX),true)
+PRODUCT_PACKAGES += com.google.aosp_cf_slim.hardware.core_permissions
+else
+PRODUCT_COPY_FILES += \
+ frameworks/native/data/etc/aosp_excluded_hardware.xml:$(TARGET_COPY_OUT_VENDOR)/etc/permissions/aosp_excluded_hardware.xml
+endif
+
+PRODUCT_NAME := aosp_cf_arm64_slim
+PRODUCT_DEVICE := vsoc_arm64_only
+PRODUCT_MANUFACTURER := Google
+PRODUCT_MODEL := Cuttlefish arm64 slim 64-bit only
+
+PRODUCT_VENDOR_PROPERTIES += \
+ ro.soc.manufacturer=$(PRODUCT_MANUFACTURER) \
+ ro.soc.model=$(PRODUCT_DEVICE)
diff --git a/vsoc_arm_only/BoardConfig.mk b/vsoc_arm_only/BoardConfig.mk
index e0cb5ce..54c656c 100644
--- a/vsoc_arm_only/BoardConfig.mk
+++ b/vsoc_arm_only/BoardConfig.mk
@@ -27,7 +27,7 @@
TARGET_CPU_ABI2 := armeabi
TARGET_CPU_VARIANT := cortex-a15
-BOARD_VENDOR_RAMDISK_KERNEL_MODULES += $(wildcard device/google/cuttlefish_prebuilts/kernel/5.4-arm/*.ko)
+BOARD_VENDOR_RAMDISK_KERNEL_MODULES += $(wildcard device/google/cuttlefish_prebuilts/kernel/$(TARGET_KERNEL_USE)-arm/*.ko)
HOST_CROSS_OS := linux_bionic
HOST_CROSS_ARCH := arm64
diff --git a/vsoc_arm_only/bootloader.mk b/vsoc_arm_only/bootloader.mk
index 93de14e..959cd61 100644
--- a/vsoc_arm_only/bootloader.mk
+++ b/vsoc_arm_only/bootloader.mk
@@ -18,5 +18,3 @@
# FIXME: Copying the QEMU bootloader for now, but this should be updated..
BOARD_PREBUILT_BOOTLOADER := \
device/google/cuttlefish_prebuilts/bootloader/qemu_arm/u-boot.bin
-PRODUCT_COPY_FILES += \
- device/google/cuttlefish_prebuilts/bootloader/qemu_arm/u-boot.bin:bootloader.qemu
diff --git a/vsoc_arm_only/kernel.mk b/vsoc_arm_only/kernel.mk
index f7472e7..a216444 100644
--- a/vsoc_arm_only/kernel.mk
+++ b/vsoc_arm_only/kernel.mk
@@ -13,6 +13,7 @@
# See the License for the specific language governing permissions and
# limitations under the License.
-TARGET_KERNEL_PATH ?= device/google/cuttlefish_prebuilts/kernel/5.4-arm/kernel-5.4
+TARGET_KERNEL_USE ?= mainline
+TARGET_KERNEL_PATH ?= device/google/cuttlefish_prebuilts/kernel/$(TARGET_KERNEL_USE)-arm/kernel-$(TARGET_KERNEL_USE)
PRODUCT_COPY_FILES += $(TARGET_KERNEL_PATH):kernel
diff --git a/vsoc_arm_only/phone/aosp_cf.mk b/vsoc_arm_only/phone/aosp_cf.mk
index 2643c90..751c503 100644
--- a/vsoc_arm_only/phone/aosp_cf.mk
+++ b/vsoc_arm_only/phone/aosp_cf.mk
@@ -44,13 +44,15 @@
system/app/PlatformCaptivePortalLogin/PlatformCaptivePortalLogin.apk \
system/priv-app/CellBroadcastServiceModulePlatform/CellBroadcastServiceModulePlatform.apk \
system/priv-app/InProcessNetworkStack/InProcessNetworkStack.apk \
- system/priv-app/PlatformNetworkPermissionConfig/PlatformNetworkPermissionConfig.apk \
#
# All components inherited here go to vendor image
#
$(call inherit-product, device/google/cuttlefish/shared/phone/device_vendor.mk)
+# TODO(b/205788876) remove this when openwrt has an image for arm.
+PRODUCT_ENFORCE_MAC80211_HWSIM := false
+
#
# Special settings for the target
#
@@ -67,5 +69,10 @@
PRODUCT_MODEL := Cuttlefish arm phone 32-bit only
PRODUCT_VENDOR_PROPERTIES += \
+ ro.config.low_ram=true \
ro.soc.manufacturer=$(PRODUCT_MANUFACTURER) \
ro.soc.model=$(PRODUCT_DEVICE)
+
+TARGET_SYSTEM_PROP += \
+ build/make/target/board/go_defaults_512.prop \
+ build/make/target/board/go_defaults_common.prop
diff --git a/vsoc_x86/BoardConfig.mk b/vsoc_x86/BoardConfig.mk
index 70db8c2..cd2c9d4 100644
--- a/vsoc_x86/BoardConfig.mk
+++ b/vsoc_x86/BoardConfig.mk
@@ -30,10 +30,8 @@
TARGET_NATIVE_BRIDGE_CPU_VARIANT := generic
TARGET_NATIVE_BRIDGE_ABI := armeabi-v7a armeabi
-BUILD_BROKEN_DUP_RULES := true
-
ifeq ($(BOARD_VENDOR_RAMDISK_KERNEL_MODULES),)
- BOARD_VENDOR_RAMDISK_KERNEL_MODULES += $(wildcard kernel/prebuilts/common-modules/virtual-device/5.10/x86-64/*.ko)
+ BOARD_VENDOR_RAMDISK_KERNEL_MODULES += $(wildcard kernel/prebuilts/common-modules/virtual-device/$(TARGET_KERNEL_USE)/x86-64/*.ko)
endif
# TODO(b/156534160): Temporarily allow for the old style PRODUCT_COPY_FILES for ndk_translation_prebuilt
diff --git a/vsoc_x86/auto/aosp_cf.mk b/vsoc_x86/auto/aosp_cf.mk
new file mode 100644
index 0000000..ecd9063
--- /dev/null
+++ b/vsoc_x86/auto/aosp_cf.mk
@@ -0,0 +1,61 @@
+#
+# Copyright (C) 2022 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+#
+# All components inherited here go to system image
+#
+$(call inherit-product, $(SRC_TARGET_DIR)/product/generic_system.mk)
+
+# FIXME: generic_system.mk sets 'PRODUCT_ENFORCE_RRO_TARGETS := *'
+# but this breaks phone_car. So undo it here.
+PRODUCT_ENFORCE_RRO_TARGETS := frameworks-res
+
+# FIXME: Disable mainline path checks
+PRODUCT_ENFORCE_ARTIFACT_PATH_REQUIREMENTS := false
+
+#
+# All components inherited here go to system_ext image
+#
+$(call inherit-product, $(SRC_TARGET_DIR)/product/base_system_ext.mk)
+
+#
+# All components inherited here go to product image
+#
+$(call inherit-product, $(SRC_TARGET_DIR)/product/aosp_product.mk)
+
+#
+# All components inherited here go to vendor image
+#
+$(call inherit-product, device/google/cuttlefish/shared/auto/device_vendor.mk)
+
+#
+# Special settings for the target
+#
+$(call inherit-product, device/google/cuttlefish/vsoc_x86_64/kernel.mk)
+$(call inherit-product, device/google/cuttlefish/vsoc_x86_64/bootloader.mk)
+
+# Exclude features that are not available on AOSP devices.
+PRODUCT_COPY_FILES += \
+ frameworks/native/data/etc/aosp_excluded_hardware.xml:$(TARGET_COPY_OUT_VENDOR)/etc/permissions/aosp_excluded_hardware.xml
+
+PRODUCT_NAME := aosp_cf_x86_auto
+PRODUCT_DEVICE := vsoc_x86
+PRODUCT_MANUFACTURER := Google
+PRODUCT_MODEL := Cuttlefish x86 auto
+
+PRODUCT_VENDOR_PROPERTIES += \
+ ro.soc.manufacturer=$(PRODUCT_MANUFACTURER) \
+ ro.soc.model=$(PRODUCT_DEVICE)
diff --git a/vsoc_x86/auto/device.mk b/vsoc_x86/auto/device.mk
deleted file mode 100644
index e0d5bad8..0000000
--- a/vsoc_x86/auto/device.mk
+++ /dev/null
@@ -1,28 +0,0 @@
-#
-# Copyright (C) 2017 The Android Open Source Project
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-#
-
-$(call inherit-product, device/google/cuttlefish/shared/auto/device.mk)
-$(call inherit-product, device/google/cuttlefish/vsoc_x86_64/kernel.mk)
-$(call inherit-product, device/google/cuttlefish/vsoc_x86_64/bootloader.mk)
-
-PRODUCT_NAME := aosp_cf_x86_auto
-PRODUCT_DEVICE := vsoc_x86
-PRODUCT_MANUFACTURER := Google
-PRODUCT_MODEL := Cuttlefish x86 auto
-
-PRODUCT_VENDOR_PROPERTIES += \
- ro.soc.manufacturer=$(PRODUCT_MANUFACTURER) \
- ro.soc.model=$(PRODUCT_DEVICE)
diff --git a/vsoc_x86/go/OWNERS b/vsoc_x86/go/OWNERS
new file mode 100644
index 0000000..0c77d0e
--- /dev/null
+++ b/vsoc_x86/go/OWNERS
@@ -0,0 +1,2 @@
[email protected]
[email protected]
diff --git a/vsoc_x86/go/aosp_cf.mk b/vsoc_x86/go/aosp_cf.mk
new file mode 100644
index 0000000..d91c575
--- /dev/null
+++ b/vsoc_x86/go/aosp_cf.mk
@@ -0,0 +1,65 @@
+#
+# Copyright (C) 2022 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+#
+# All components inherited here go to system image (same as GSI system)
+#
+$(call inherit-product, build/target/product/go_defaults.mk)
+$(call inherit-product, $(SRC_TARGET_DIR)/product/generic_system.mk)
+
+PRODUCT_ENFORCE_ARTIFACT_PATH_REQUIREMENTS := relaxed
+
+# These packages come from go_defaults.mk
+PRODUCT_ARTIFACT_PATH_REQUIREMENT_ALLOWED_LIST += \
+ system/apex/com.android.tethering.inprocess.capex \
+ system/app/PlatformCaptivePortalLogin/PlatformCaptivePortalLogin.apk \
+ system/priv-app/CellBroadcastServiceModulePlatform/CellBroadcastServiceModulePlatform.apk \
+ system/priv-app/InProcessNetworkStack/InProcessNetworkStack.apk \
+
+#
+# All components inherited here go to system_ext image (same as GSI system_ext)
+#
+$(call inherit-product, $(SRC_TARGET_DIR)/product/handheld_system_ext.mk)
+$(call inherit-product, $(SRC_TARGET_DIR)/product/telephony_system_ext.mk)
+
+#
+# All components inherited here go to product image (same as GSI product)
+#
+$(call inherit-product, $(SRC_TARGET_DIR)/product/aosp_product.mk)
+
+#
+# All components inherited here go to vendor image
+#
+$(call inherit-product, device/google/cuttlefish/shared/go/device_vendor.mk)
+
+#
+# Special settings for the target
+#
+$(call inherit-product, device/google/cuttlefish/vsoc_x86_64/kernel.mk)
+$(call inherit-product, device/google/cuttlefish/vsoc_x86_64/bootloader.mk)
+
+# Exclude features that are not available on AOSP devices.
+PRODUCT_COPY_FILES += \
+ frameworks/native/data/etc/aosp_excluded_hardware.xml:$(TARGET_COPY_OUT_VENDOR)/etc/permissions/aosp_excluded_hardware.xml
+
+PRODUCT_NAME := aosp_cf_x86_go_phone
+PRODUCT_DEVICE := vsoc_x86
+PRODUCT_MANUFACTURER := Google
+PRODUCT_MODEL := Cuttlefish x86 Go phone
+
+PRODUCT_VENDOR_PROPERTIES += \
+ ro.soc.manufacturer=$(PRODUCT_MANUFACTURER) \
+ ro.soc.model=$(PRODUCT_DEVICE)
diff --git a/vsoc_x86/go_512_phone/device.mk b/vsoc_x86/go_512_phone/device.mk
deleted file mode 100644
index 2df9023..0000000
--- a/vsoc_x86/go_512_phone/device.mk
+++ /dev/null
@@ -1,28 +0,0 @@
-#
-# Copyright (C) 2018 The Android Open Source Project
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-#
-
-$(call inherit-product, device/google/cuttlefish/shared/go_512/device.mk)
-$(call inherit-product, device/google/cuttlefish/vsoc_x86_64/kernel.mk)
-
-PRODUCT_NAME := aosp_cf_x86_go_512_phone
-PRODUCT_DEVICE := vsoc_x86
-PRODUCT_MANUFACTURER := Google
-PRODUCT_MODEL := Cuttlefish x86 Go 512 phone
-PRODUCT_PACKAGE_OVERLAYS := device/google/cuttlefish/vsoc_x86/phone/overlay
-
-PRODUCT_VENDOR_PROPERTIES += \
- ro.soc.manufacturer=$(PRODUCT_MANUFACTURER) \
- ro.soc.model=$(PRODUCT_DEVICE)
diff --git a/vsoc_x86/go_phone/device.mk b/vsoc_x86/go_phone/device.mk
deleted file mode 100644
index 8b2a8f3..0000000
--- a/vsoc_x86/go_phone/device.mk
+++ /dev/null
@@ -1,28 +0,0 @@
-#
-# Copyright (C) 2018 The Android Open Source Project
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-#
-
-$(call inherit-product, device/google/cuttlefish/shared/go/device.mk)
-$(call inherit-product, device/google/cuttlefish/vsoc_x86_64/kernel.mk)
-
-PRODUCT_NAME := aosp_cf_x86_go_phone
-PRODUCT_DEVICE := vsoc_x86
-PRODUCT_MANUFACTURER := Google
-PRODUCT_MODEL := Cuttlefish x86 Go phone
-PRODUCT_PACKAGE_OVERLAYS := device/google/cuttlefish/vsoc_x86/phone/overlay
-
-PRODUCT_VENDOR_PROPERTIES += \
- ro.soc.manufacturer=$(PRODUCT_MANUFACTURER) \
- ro.soc.model=$(PRODUCT_DEVICE)
diff --git a/vsoc_x86/tv/aosp_cf.mk b/vsoc_x86/tv/aosp_cf.mk
new file mode 100644
index 0000000..757c0a7
--- /dev/null
+++ b/vsoc_x86/tv/aosp_cf.mk
@@ -0,0 +1,56 @@
+#
+# Copyright (C) 2019 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+#
+# All components inherited here go to system image
+#
+$(call inherit-product, device/google/atv/products/atv_generic_system.mk)
+
+PRODUCT_ENFORCE_ARTIFACT_PATH_REQUIREMENTS := relaxed
+
+#
+# All components inherited here go to system_ext image
+#
+$(call inherit-product, device/google/atv/products/atv_system_ext.mk)
+
+#
+# All components inherited here go to product image
+#
+$(call inherit-product, device/google/atv/products/atv_product.mk)
+
+#
+# All components inherited here go to vendor image
+#
+$(call inherit-product, device/google/cuttlefish/shared/tv/device_vendor.mk)
+
+#
+# Special settings for the target
+#
+$(call inherit-product, device/google/cuttlefish/vsoc_x86_64/kernel.mk)
+$(call inherit-product, device/google/cuttlefish/vsoc_x86_64/bootloader.mk)
+
+# Exclude features that are not available on AOSP devices.
+PRODUCT_COPY_FILES += \
+ frameworks/native/data/etc/aosp_excluded_hardware.xml:$(TARGET_COPY_OUT_VENDOR)/etc/permissions/aosp_excluded_hardware.xml
+
+PRODUCT_NAME := aosp_cf_x86_tv
+PRODUCT_DEVICE := vsoc_x86
+PRODUCT_MANUFACTURER := Google
+PRODUCT_MODEL := Cuttlefish x86 tv
+
+PRODUCT_VENDOR_PROPERTIES += \
+ ro.soc.manufacturer=$(PRODUCT_MANUFACTURER) \
+ ro.soc.model=$(PRODUCT_DEVICE)
diff --git a/vsoc_x86/tv/device.mk b/vsoc_x86/tv/device.mk
deleted file mode 100644
index b65f50c..0000000
--- a/vsoc_x86/tv/device.mk
+++ /dev/null
@@ -1,28 +0,0 @@
-#
-# Copyright (C) 2017 The Android Open Source Project
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-#
-
-$(call inherit-product, device/google/cuttlefish/shared/tv/device.mk)
-$(call inherit-product, device/google/cuttlefish/vsoc_x86_64/kernel.mk)
-$(call inherit-product, device/google/cuttlefish/vsoc_x86_64/bootloader.mk)
-
-PRODUCT_NAME := aosp_cf_x86_tv
-PRODUCT_DEVICE := vsoc_x86
-PRODUCT_MANUFACTURER := Google
-PRODUCT_MODEL := Cuttlefish x86 tv
-
-PRODUCT_VENDOR_PROPERTIES += \
- ro.soc.manufacturer=$(PRODUCT_MANUFACTURER) \
- ro.soc.model=$(PRODUCT_DEVICE)
diff --git a/vsoc_x86/wear/aosp_cf.mk b/vsoc_x86/wear/aosp_cf.mk
new file mode 100644
index 0000000..35402f0
--- /dev/null
+++ b/vsoc_x86/wear/aosp_cf.mk
@@ -0,0 +1,77 @@
+#
+# Copyright (C) 2022 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+#
+# All components inherited here go to system image
+#
+$(call inherit-product, device/google/cuttlefish/shared/wear/aosp_system.mk)
+
+# Allowed for wearables, but not installed to /system by default
+PRODUCT_PACKAGES += \
+ cameraserver \
+
+# Cuttlefish uses A/B with system_b preopt, so we must install these
+PRODUCT_PACKAGES += \
+ cppreopts.sh \
+ otapreopt_script \
+
+# Hacks to boot with basic AOSP system apps
+PRODUCT_PACKAGES += \
+ Contacts \
+ Launcher3QuickStep \
+ Provision \
+ Settings \
+ StorageManager \
+ SystemUI \
+
+PRODUCT_COPY_FILES += \
+ frameworks/native/data/etc/android.software.app_widgets.xml:$(TARGET_COPY_OUT_VENDOR)/etc/permissions/android.software.app_widgets.xml \
+
+#
+# All components inherited here go to system_ext image
+#
+$(call inherit-product, device/google/cuttlefish/shared/wear/aosp_system_ext.mk)
+
+#
+# All components inherited here go to product image
+#
+$(call inherit-product, device/google/cuttlefish/shared/wear/aosp_product.mk)
+
+#
+# All components inherited here go to vendor image
+#
+$(call inherit-product, device/google/cuttlefish/shared/wear/aosp_vendor.mk)
+$(call inherit-product, device/google/cuttlefish/shared/wear/device_vendor.mk)
+PRODUCT_ENFORCE_MAC80211_HWSIM := false
+
+#
+# Special settings for the target
+#
+$(call inherit-product, device/google/cuttlefish/vsoc_x86_64/kernel.mk)
+$(call inherit-product, device/google/cuttlefish/vsoc_x86_64/bootloader.mk)
+
+# Exclude features that are not available on AOSP devices.
+PRODUCT_COPY_FILES += \
+ frameworks/native/data/etc/aosp_excluded_hardware.xml:$(TARGET_COPY_OUT_VENDOR)/etc/permissions/aosp_excluded_hardware.xml \
+
+PRODUCT_NAME := aosp_cf_x86_wear
+PRODUCT_DEVICE := vsoc_x86
+PRODUCT_MANUFACTURER := Google
+PRODUCT_MODEL := Cuttlefish x86 wearable
+
+PRODUCT_VENDOR_PROPERTIES += \
+ ro.soc.manufacturer=$(PRODUCT_MANUFACTURER) \
+ ro.soc.model=$(PRODUCT_DEVICE)
diff --git a/vsoc_x86_64/BoardConfig.mk b/vsoc_x86_64/BoardConfig.mk
index 52dde5c..a740a76 100644
--- a/vsoc_x86_64/BoardConfig.mk
+++ b/vsoc_x86_64/BoardConfig.mk
@@ -28,7 +28,6 @@
TARGET_2ND_ARCH := x86
TARGET_2ND_CPU_ABI := x86
TARGET_2ND_ARCH_VARIANT := silvermont
-TARGET_2ND_CPU_VARIANT := silvermont
TARGET_NATIVE_BRIDGE_ARCH := arm64
TARGET_NATIVE_BRIDGE_ARCH_VARIANT := armv8-a
@@ -40,8 +39,6 @@
TARGET_NATIVE_BRIDGE_2ND_CPU_VARIANT := generic
TARGET_NATIVE_BRIDGE_2ND_ABI := armeabi-v7a armeabi
-BUILD_BROKEN_DUP_RULES := true
-
ifeq ($(BOARD_VENDOR_RAMDISK_KERNEL_MODULES),)
BOARD_VENDOR_RAMDISK_KERNEL_MODULES += $(wildcard kernel/prebuilts/common-modules/virtual-device/$(TARGET_KERNEL_USE)/x86-64/*.ko)
endif
diff --git a/vsoc_x86_64/auto/aosp_cf.mk b/vsoc_x86_64/auto/aosp_cf.mk
new file mode 100644
index 0000000..610c8c1
--- /dev/null
+++ b/vsoc_x86_64/auto/aosp_cf.mk
@@ -0,0 +1,62 @@
+#
+# Copyright (C) 2022 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+#
+# All components inherited here go to system image
+#
+$(call inherit-product, $(SRC_TARGET_DIR)/product/core_64_bit.mk)
+$(call inherit-product, $(SRC_TARGET_DIR)/product/generic_system.mk)
+
+# FIXME: generic_system.mk sets 'PRODUCT_ENFORCE_RRO_TARGETS := *'
+# but this breaks phone_car. So undo it here.
+PRODUCT_ENFORCE_RRO_TARGETS := frameworks-res
+
+# FIXME: Disable mainline path checks
+PRODUCT_ENFORCE_ARTIFACT_PATH_REQUIREMENTS := false
+
+#
+# All components inherited here go to system_ext image
+#
+$(call inherit-product, $(SRC_TARGET_DIR)/product/base_system_ext.mk)
+
+#
+# All components inherited here go to product image
+#
+$(call inherit-product, $(SRC_TARGET_DIR)/product/aosp_product.mk)
+
+#
+# All components inherited here go to vendor image
+#
+$(call inherit-product, device/google/cuttlefish/shared/auto/device_vendor.mk)
+
+#
+# Special settings for the target
+#
+$(call inherit-product, device/google/cuttlefish/vsoc_x86_64/kernel.mk)
+$(call inherit-product, device/google/cuttlefish/vsoc_x86_64/bootloader.mk)
+
+# Exclude features that are not available on AOSP devices.
+PRODUCT_COPY_FILES += \
+ frameworks/native/data/etc/aosp_excluded_hardware.xml:$(TARGET_COPY_OUT_VENDOR)/etc/permissions/aosp_excluded_hardware.xml
+
+PRODUCT_NAME := aosp_cf_x86_64_auto
+PRODUCT_DEVICE := vsoc_x86_64
+PRODUCT_MANUFACTURER := Google
+PRODUCT_MODEL := Cuttlefish x86_64 auto
+
+PRODUCT_VENDOR_PROPERTIES += \
+ ro.soc.manufacturer=$(PRODUCT_MANUFACTURER) \
+ ro.soc.model=$(PRODUCT_DEVICE)
diff --git a/vsoc_x86_64/auto/device.mk b/vsoc_x86_64/auto/device.mk
deleted file mode 100644
index b21f694..0000000
--- a/vsoc_x86_64/auto/device.mk
+++ /dev/null
@@ -1,29 +0,0 @@
-#
-# Copyright (C) 2020 The Android Open Source Project
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-#
-
-$(call inherit-product, $(SRC_TARGET_DIR)/product/core_64_bit_only.mk)
-$(call inherit-product, device/google/cuttlefish/shared/auto/device.mk)
-$(call inherit-product, device/google/cuttlefish/vsoc_x86_64/kernel.mk)
-$(call inherit-product, device/google/cuttlefish/vsoc_x86_64/bootloader.mk)
-
-PRODUCT_NAME := aosp_cf_x86_64_auto
-PRODUCT_DEVICE := vsoc_x86_64
-PRODUCT_MANUFACTURER := Google
-PRODUCT_MODEL := Cuttlefish x86_64 auto
-
-PRODUCT_VENDOR_PROPERTIES += \
- ro.soc.manufacturer=$(PRODUCT_MANUFACTURER) \
- ro.soc.model=$(PRODUCT_DEVICE)
diff --git a/vsoc_x86_64/bootloader.mk b/vsoc_x86_64/bootloader.mk
index b0d8c5d..6294ca7 100644
--- a/vsoc_x86_64/bootloader.mk
+++ b/vsoc_x86_64/bootloader.mk
@@ -17,5 +17,3 @@
TARGET_NO_BOOTLOADER := false
BOARD_PREBUILT_BOOTLOADER := \
device/google/cuttlefish_prebuilts/bootloader/crosvm_x86_64/u-boot.rom
-PRODUCT_COPY_FILES += \
- device/google/cuttlefish_prebuilts/bootloader/qemu_x86_64/u-boot.rom:bootloader.qemu
diff --git a/vsoc_x86_64/kernel.mk b/vsoc_x86_64/kernel.mk
index 112eb25..e087f22 100644
--- a/vsoc_x86_64/kernel.mk
+++ b/vsoc_x86_64/kernel.mk
@@ -13,7 +13,7 @@
# See the License for the specific language governing permissions and
# limitations under the License.
-TARGET_KERNEL_USE ?= 5.10
+TARGET_KERNEL_USE ?= 5.15
TARGET_KERNEL_PATH ?= kernel/prebuilts/$(TARGET_KERNEL_USE)/x86_64/kernel-$(TARGET_KERNEL_USE)
PRODUCT_COPY_FILES += $(TARGET_KERNEL_PATH):kernel
diff --git a/vsoc_x86_64/phone/aosp_cf.mk b/vsoc_x86_64/phone/aosp_cf.mk
index 478452f..2be93fd 100644
--- a/vsoc_x86_64/phone/aosp_cf.mk
+++ b/vsoc_x86_64/phone/aosp_cf.mk
@@ -36,6 +36,7 @@
#
# All components inherited here go to vendor image
#
+LOCAL_PREFER_VENDOR_APEX := true
$(call inherit-product, device/google/cuttlefish/shared/phone/device_vendor.mk)
# Nested virtualization support
@@ -48,14 +49,24 @@
$(call inherit-product, device/google/cuttlefish/vsoc_x86_64/bootloader.mk)
# Exclude features that are not available on AOSP devices.
+ifeq ($(LOCAL_PREFER_VENDOR_APEX),true)
+PRODUCT_PACKAGES += com.google.aosp_cf_phone.hardware.core_permissions
+else
PRODUCT_COPY_FILES += \
frameworks/native/data/etc/aosp_excluded_hardware.xml:$(TARGET_COPY_OUT_VENDOR)/etc/permissions/aosp_excluded_hardware.xml
+endif
PRODUCT_NAME := aosp_cf_x86_64_phone
PRODUCT_DEVICE := vsoc_x86_64
PRODUCT_MANUFACTURER := Google
PRODUCT_MODEL := Cuttlefish x86_64 phone
+# Window sidecar and extensions to enhance activity embedding, multi-display,
+# tablet, and foldable support.
+PRODUCT_PACKAGES += \
+ androidx.window.extensions \
+ androidx.window.sidecar \
+
PRODUCT_VENDOR_PROPERTIES += \
ro.soc.manufacturer=$(PRODUCT_MANUFACTURER) \
ro.soc.model=$(PRODUCT_DEVICE)
diff --git a/vsoc_x86_64/phone/aosp_cf_foldable.mk b/vsoc_x86_64/phone/aosp_cf_foldable.mk
index c0872d8..b8a0a16 100644
--- a/vsoc_x86_64/phone/aosp_cf_foldable.mk
+++ b/vsoc_x86_64/phone/aosp_cf_foldable.mk
@@ -25,14 +25,7 @@
device/google/cuttlefish/shared/foldable/display_layout_configuration.xml:$(TARGET_COPY_OUT_VENDOR)/etc/displayconfig/display_layout_configuration.xml \
device/google/cuttlefish/shared/foldable/display_settings.xml:$(TARGET_COPY_OUT_VENDOR)/etc/display_settings.xml \
-# Sidecar and window extensions enhance multi-display, tablet, and foldable support.
-PRODUCT_PACKAGES += \
- androidx.window.extensions \
- androidx.window.sidecar \
-
# Include RRO settings that specify the fold states and screen information.
DEVICE_PACKAGE_OVERLAYS += device/google/cuttlefish/shared/foldable/overlay
-# Include the foldable `launch_cvd --config foldable` option.
-SOONG_CONFIG_cvd_launch_configs += cvd_config_foldable.json
# Include the android-info.txt that specifies the foldable --config by default.
TARGET_BOARD_INFO_FILE := device/google/cuttlefish/shared/foldable/android-info.txt
diff --git a/vsoc_x86_64/tv/OWNERS b/vsoc_x86_64/tv/OWNERS
new file mode 100644
index 0000000..4df9f27
--- /dev/null
+++ b/vsoc_x86_64/tv/OWNERS
@@ -0,0 +1,2 @@
+# Bug component: 760438
+include device/google/atv:/OWNERS
diff --git a/vsoc_x86_64/tv/aosp_cf.mk b/vsoc_x86_64/tv/aosp_cf.mk
new file mode 100644
index 0000000..c59f1b0
--- /dev/null
+++ b/vsoc_x86_64/tv/aosp_cf.mk
@@ -0,0 +1,57 @@
+#
+# Copyright (C) 2019 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+#
+# All components inherited here go to system image
+#
+$(call inherit-product, $(SRC_TARGET_DIR)/product/core_64_bit.mk)
+$(call inherit-product, device/google/atv/products/atv_generic_system.mk)
+
+PRODUCT_ENFORCE_ARTIFACT_PATH_REQUIREMENTS := relaxed
+
+#
+# All components inherited here go to system_ext image
+#
+$(call inherit-product, device/google/atv/products/atv_system_ext.mk)
+
+#
+# All components inherited here go to product image
+#
+$(call inherit-product, device/google/atv/products/atv_product.mk)
+
+#
+# All components inherited here go to vendor image
+#
+$(call inherit-product, device/google/cuttlefish/shared/tv/device_vendor.mk)
+
+#
+# Special settings for the target
+#
+$(call inherit-product, device/google/cuttlefish/vsoc_x86_64/kernel.mk)
+$(call inherit-product, device/google/cuttlefish/vsoc_x86_64/bootloader.mk)
+
+# Exclude features that are not available on AOSP devices.
+PRODUCT_COPY_FILES += \
+ frameworks/native/data/etc/aosp_excluded_hardware.xml:$(TARGET_COPY_OUT_VENDOR)/etc/permissions/aosp_excluded_hardware.xml
+
+PRODUCT_NAME := aosp_cf_x86_64_tv
+PRODUCT_DEVICE := vsoc_x86_64
+PRODUCT_MANUFACTURER := Google
+PRODUCT_MODEL := Cuttlefish x86_64 tv
+
+PRODUCT_VENDOR_PROPERTIES += \
+ ro.soc.manufacturer=$(PRODUCT_MANUFACTURER) \
+ ro.soc.model=$(PRODUCT_DEVICE)
diff --git a/vsoc_x86_64/tv/device.mk b/vsoc_x86_64/tv/device.mk
deleted file mode 100644
index d25210d..0000000
--- a/vsoc_x86_64/tv/device.mk
+++ /dev/null
@@ -1,29 +0,0 @@
-#
-# Copyright (C) 2021 The Android Open Source Project
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-#
-
-$(call inherit-product, $(SRC_TARGET_DIR)/product/core_64_bit.mk)
-$(call inherit-product, device/google/cuttlefish/shared/tv/device.mk)
-$(call inherit-product, device/google/cuttlefish/vsoc_x86_64/kernel.mk)
-$(call inherit-product, device/google/cuttlefish/vsoc_x86_64/bootloader.mk)
-
-PRODUCT_NAME := aosp_cf_x86_64_tv
-PRODUCT_DEVICE := vsoc_x86_64
-PRODUCT_MANUFACTURER := Google
-PRODUCT_MODEL := Cuttlefish x86_64 tv
-
-PRODUCT_VENDOR_PROPERTIES += \
- ro.soc.manufacturer=$(PRODUCT_MANUFACTURER) \
- ro.soc.model=$(PRODUCT_DEVICE)
diff --git a/vsoc_x86_64_only/BoardConfig.mk b/vsoc_x86_64_only/BoardConfig.mk
index d702533..7185b2d 100644
--- a/vsoc_x86_64_only/BoardConfig.mk
+++ b/vsoc_x86_64_only/BoardConfig.mk
@@ -31,5 +31,4 @@
TARGET_NATIVE_BRIDGE_ABI := arm64-v8a
AUDIOSERVER_MULTILIB := first
-BUILD_BROKEN_DUP_RULES := true
-BOARD_VENDOR_RAMDISK_KERNEL_MODULES += $(wildcard kernel/prebuilts/common-modules/virtual-device/5.10/x86-64/*.ko)
+BOARD_VENDOR_RAMDISK_KERNEL_MODULES += $(wildcard kernel/prebuilts/common-modules/virtual-device/$(TARGET_KERNEL_USE)/x86-64/*.ko)
diff --git a/vsoc_x86_64_only/slim/aosp_cf.mk b/vsoc_x86_64_only/slim/aosp_cf.mk
new file mode 100644
index 0000000..c7ca328
--- /dev/null
+++ b/vsoc_x86_64_only/slim/aosp_cf.mk
@@ -0,0 +1,65 @@
+#
+# Copyright (C) 2022 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+#
+# All components inherited here go to system image
+#
+$(call inherit-product, $(SRC_TARGET_DIR)/product/core_64_bit_only.mk)
+$(call inherit-product, $(SRC_TARGET_DIR)/product/generic_system.mk)
+
+PRODUCT_ENFORCE_ARTIFACT_PATH_REQUIREMENTS := relaxed
+
+#
+# All components inherited here go to system_ext image
+#
+$(call inherit-product, $(SRC_TARGET_DIR)/product/media_system_ext.mk)
+$(call inherit-product, $(SRC_TARGET_DIR)/product/telephony_system_ext.mk)
+
+#
+# All components inherited here go to product image
+#
+$(call inherit-product, $(SRC_TARGET_DIR)/product/media_product.mk)
+PRODUCT_PACKAGES += FakeSystemApp
+
+#
+# All components inherited here go to vendor image
+#
+LOCAL_DISABLE_OMX := true
+LOCAL_PREFER_VENDOR_APEX := true
+$(call inherit-product, device/google/cuttlefish/shared/slim/device_vendor.mk)
+
+#
+# Special settings for the target
+#
+$(call inherit-product, device/google/cuttlefish/vsoc_x86_64/kernel.mk)
+$(call inherit-product, device/google/cuttlefish/vsoc_x86_64/bootloader.mk)
+
+# Exclude features that are not available on AOSP devices.
+ifeq ($(LOCAL_PREFER_VENDOR_APEX),true)
+PRODUCT_PACKAGES += com.google.aosp_cf_slim.hardware.core_permissions
+else
+PRODUCT_COPY_FILES += \
+ frameworks/native/data/etc/aosp_excluded_hardware.xml:$(TARGET_COPY_OUT_VENDOR)/etc/permissions/aosp_excluded_hardware.xml
+endif
+
+PRODUCT_NAME := aosp_cf_x86_64_slim
+PRODUCT_DEVICE := vsoc_x86_64_only
+PRODUCT_MANUFACTURER := Google
+PRODUCT_MODEL := Cuttlefish x86_64 slim 64-bit only
+
+PRODUCT_VENDOR_PROPERTIES += \
+ ro.soc.manufacturer=$(PRODUCT_MANUFACTURER) \
+ ro.soc.model=$(PRODUCT_DEVICE)
diff --git a/vsoc_x86_noapex/aosp_cf_noapex.mk b/vsoc_x86_noapex/aosp_cf_noapex.mk
deleted file mode 100644
index aa5d3b2..0000000
--- a/vsoc_x86_noapex/aosp_cf_noapex.mk
+++ /dev/null
@@ -1,31 +0,0 @@
-#
-# Copyright (C) 2019 The Android Open Source Project
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-#
-
-# Order of this and the following statements is important.
-# Putting this first in the list takes precedence over the one inherited from
-# aosp_cf.
-OVERRIDE_TARGET_FLATTEN_APEX := true
-
-$(call inherit-product, device/google/cuttlefish/vsoc_x86/phone/aosp_cf.mk)
-
-PRODUCT_NAME := aosp_cf_x86_phone_noapex
-PRODUCT_DEVICE := vsoc_x86_noapex
-PRODUCT_MANUFACTURER := Google
-PRODUCT_MODEL := Cuttlefish x86 phone without APEX support
-
-PRODUCT_VENDOR_PROPERTIES += \
- ro.soc.manufacturer=$(PRODUCT_MANUFACTURER) \
- ro.soc.model=$(PRODUCT_DEVICE)
diff --git a/vsoc_x86_only/kernel.mk b/vsoc_x86_only/kernel.mk
index d06c0a1..0eec8d9 100644
--- a/vsoc_x86_only/kernel.mk
+++ b/vsoc_x86_only/kernel.mk
@@ -13,7 +13,7 @@
# See the License for the specific language governing permissions and
# limitations under the License.
-TARGET_KERNEL_USE ?= 5.10
+TARGET_KERNEL_USE ?= 5.15
TARGET_KERNEL_PATH ?= device/google/cuttlefish_prebuilts/kernel/$(TARGET_KERNEL_USE)-i686/kernel-$(TARGET_KERNEL_USE)
PRODUCT_COPY_FILES +=$(TARGET_KERNEL_PATH):kernel