Merge "CertificateUtils to kotlin" into main
diff --git a/android/TerminalApp/java/com/android/virtualization/terminal/Runner.java b/android/TerminalApp/java/com/android/virtualization/terminal/Runner.java
deleted file mode 100644
index 4094025..0000000
--- a/android/TerminalApp/java/com/android/virtualization/terminal/Runner.java
+++ /dev/null
@@ -1,115 +0,0 @@
-/*
- * Copyright (C) 2024 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT 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.virtualization.terminal;
-
-import static com.android.virtualization.terminal.MainActivity.TAG;
-
-import android.content.Context;
-import android.system.virtualmachine.VirtualMachine;
-import android.system.virtualmachine.VirtualMachineCallback;
-import android.system.virtualmachine.VirtualMachineConfig;
-import android.system.virtualmachine.VirtualMachineCustomImageConfig;
-import android.system.virtualmachine.VirtualMachineException;
-import android.system.virtualmachine.VirtualMachineManager;
-import android.util.Log;
-
-import java.util.concurrent.CompletableFuture;
-import java.util.concurrent.ForkJoinPool;
-
-/** Utility class for creating a VM and waiting for it to finish. */
-class Runner {
- private final VirtualMachine mVirtualMachine;
- private final Callback mCallback;
-
- private Runner(VirtualMachine vm, Callback cb) {
- mVirtualMachine = vm;
- mCallback = cb;
- }
-
- /** Create a virtual machine of the given config, under the given context. */
- static Runner create(Context context, VirtualMachineConfig config)
- throws VirtualMachineException {
- // context may already be the app context, but calling this again is not harmful.
- // See b/359439878 on why vmm should be obtained from the app context.
- Context appContext = context.getApplicationContext();
- VirtualMachineManager vmm = appContext.getSystemService(VirtualMachineManager.class);
- VirtualMachineCustomImageConfig customConfig = config.getCustomImageConfig();
- if (customConfig == null) {
- throw new RuntimeException("CustomImageConfig is missing");
- }
-
- String name = customConfig.getName();
- if (name == null || name.isEmpty()) {
- throw new RuntimeException("Virtual machine's name is missing in the config");
- }
-
- VirtualMachine vm = vmm.getOrCreate(name, config);
- try {
- vm.setConfig(config);
- } catch (VirtualMachineException e) {
- vmm.delete(name);
- vm = vmm.create(name, config);
- Log.w(TAG, "Re-creating virtual machine (" + name + ")", e);
- }
-
- Callback cb = new Callback();
- vm.setCallback(ForkJoinPool.commonPool(), cb);
- vm.run();
- return new Runner(vm, cb);
- }
-
- /** Give access to the underlying VirtualMachine object. */
- VirtualMachine getVm() {
- return mVirtualMachine;
- }
-
- /** Get future about VM's exit status. */
- CompletableFuture<Boolean> getExitStatus() {
- return mCallback.mFinishedSuccessfully;
- }
-
- private static class Callback implements VirtualMachineCallback {
- final CompletableFuture<Boolean> mFinishedSuccessfully = new CompletableFuture<>();
-
- @Override
- public void onPayloadStarted(VirtualMachine vm) {
- // This event is only from Microdroid-based VM. Custom VM shouldn't emit this.
- }
-
- @Override
- public void onPayloadReady(VirtualMachine vm) {
- // This event is only from Microdroid-based VM. Custom VM shouldn't emit this.
- }
-
- @Override
- public void onPayloadFinished(VirtualMachine vm, int exitCode) {
- // This event is only from Microdroid-based VM. Custom VM shouldn't emit this.
- }
-
- @Override
- public void onError(VirtualMachine vm, int errorCode, String message) {
- Log.e(TAG, "Error from VM. code: " + errorCode + " (" + message + ")");
- mFinishedSuccessfully.complete(false);
- }
-
- @Override
- public void onStopped(VirtualMachine vm, int reason) {
- Log.d(TAG, "VM stopped. Reason: " + reason);
- mFinishedSuccessfully.complete(true);
- }
- }
-}
diff --git a/android/TerminalApp/java/com/android/virtualization/terminal/Runner.kt b/android/TerminalApp/java/com/android/virtualization/terminal/Runner.kt
new file mode 100644
index 0000000..86dadbe
--- /dev/null
+++ b/android/TerminalApp/java/com/android/virtualization/terminal/Runner.kt
@@ -0,0 +1,93 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.virtualization.terminal
+
+import android.content.Context
+import android.system.virtualmachine.VirtualMachine
+import android.system.virtualmachine.VirtualMachineCallback
+import android.system.virtualmachine.VirtualMachineConfig
+import android.system.virtualmachine.VirtualMachineException
+import android.system.virtualmachine.VirtualMachineManager
+import android.util.Log
+import com.android.virtualization.terminal.MainActivity.TAG
+import java.util.concurrent.CompletableFuture
+import java.util.concurrent.ForkJoinPool
+
+/** Utility class for creating a VM and waiting for it to finish. */
+internal class Runner private constructor(val vm: VirtualMachine?, callback: Callback) {
+ /** Get future about VM's exit status. */
+ val exitStatus = callback.finishedSuccessfully
+
+ private class Callback : VirtualMachineCallback {
+ val finishedSuccessfully: CompletableFuture<Boolean?> = CompletableFuture<Boolean?>()
+
+ override fun onPayloadStarted(vm: VirtualMachine) {
+ // This event is only from Microdroid-based VM. Custom VM shouldn't emit this.
+ }
+
+ override fun onPayloadReady(vm: VirtualMachine) {
+ // This event is only from Microdroid-based VM. Custom VM shouldn't emit this.
+ }
+
+ override fun onPayloadFinished(vm: VirtualMachine, exitCode: Int) {
+ // This event is only from Microdroid-based VM. Custom VM shouldn't emit this.
+ }
+
+ override fun onError(vm: VirtualMachine, errorCode: Int, message: String) {
+ Log.e(TAG, "Error from VM. code: $errorCode ($message)")
+ finishedSuccessfully.complete(false)
+ }
+
+ override fun onStopped(vm: VirtualMachine, reason: Int) {
+ Log.d(TAG, "VM stopped. Reason: $reason")
+ finishedSuccessfully.complete(true)
+ }
+ }
+
+ companion object {
+ /** Create a virtual machine of the given config, under the given context. */
+ @JvmStatic
+ @Throws(VirtualMachineException::class)
+ fun create(context: Context, config: VirtualMachineConfig): Runner {
+ // context may already be the app context, but calling this again is not harmful.
+ // See b/359439878 on why vmm should be obtained from the app context.
+ val appContext = context.getApplicationContext()
+ val vmm =
+ appContext.getSystemService<VirtualMachineManager>(
+ VirtualMachineManager::class.java
+ )
+ val customConfig = config.customImageConfig
+ requireNotNull(customConfig) { "CustomImageConfig is missing" }
+
+ val name = customConfig.name
+ require(!name.isNullOrEmpty()) { "Virtual machine's name is missing in the config" }
+
+ var vm = vmm.getOrCreate(name, config)
+ try {
+ vm.config = config
+ } catch (e: VirtualMachineException) {
+ vmm.delete(name)
+ vm = vmm.create(name, config)
+ Log.w(TAG, "Re-creating virtual machine ($name)", e)
+ }
+
+ val cb = Callback()
+ vm.setCallback(ForkJoinPool.commonPool(), cb)
+ vm.run()
+ return Runner(vm, cb)
+ }
+ }
+}
diff --git a/libs/libavf/Android.bp b/libs/libavf/Android.bp
index 079f4ae..b583e21 100644
--- a/libs/libavf/Android.bp
+++ b/libs/libavf/Android.bp
@@ -10,6 +10,7 @@
source_stem: "bindings",
bindgen_flags: ["--default-enum-style rust"],
apex_available: ["com.android.virt"],
+ visibility: ["//packages/modules/Virtualization/tests/vts"],
}
rust_defaults {
diff --git a/tests/vts/Android.bp b/tests/vts/Android.bp
new file mode 100644
index 0000000..35fbcdc
--- /dev/null
+++ b/tests/vts/Android.bp
@@ -0,0 +1,35 @@
+prebuilt_etc {
+ name: "vts_libavf_test_kernel",
+ filename: "rialto.bin",
+ src: ":empty_file",
+ target: {
+ android_arm64: {
+ src: ":rialto_signed",
+ },
+ },
+ installable: false,
+ visibility: ["//visibility:private"],
+}
+
+rust_test {
+ name: "vts_libavf_test",
+ crate_name: "vts_libavf_test",
+ srcs: ["src/vts_libavf_test.rs"],
+ rustlibs: [
+ "libanyhow",
+ "libavf_bindgen",
+ "libciborium",
+ "liblog_rust",
+ "libscopeguard",
+ "libservice_vm_comm",
+ "libvsock",
+ ],
+ shared_libs: ["libavf"],
+ test_suites: [
+ "general-tests",
+ "vts",
+ ],
+ data: [":vts_libavf_test_kernel"],
+ test_config: "AndroidTest.xml",
+ compile_multilib: "first",
+}
diff --git a/tests/vts/AndroidTest.xml b/tests/vts/AndroidTest.xml
new file mode 100644
index 0000000..75c8d31
--- /dev/null
+++ b/tests/vts/AndroidTest.xml
@@ -0,0 +1,37 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2024 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT 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 vts_libavf_test.">
+ <option name="test-suite-tag" value="vts" />
+
+ <target_preparer class="com.android.tradefed.targetprep.RootTargetPreparer" />
+
+ <target_preparer class="com.android.tradefed.targetprep.PushFilePreparer">
+ <option name="cleanup" value="true" />
+ <option name="push" value="vts_libavf_test->/data/local/tmp/vts_libavf_test" />
+ <option name="push" value="rialto.bin->/data/local/tmp/rialto.bin" />
+ </target_preparer>
+
+ <object type="module_controller" class="com.android.tradefed.testtype.suite.module.ArchModuleController">
+ <option name="arch" value="arm64" />
+ </object>
+
+ <test class="com.android.tradefed.testtype.rust.RustBinaryTest" >
+ <option name="test-device-path" value="/data/local/tmp" />
+ <option name="module-name" value="vts_libavf_test" />
+ <!-- rialto uses a fixed port number for the host, can't run two tests at the same time -->
+ <option name="native-test-flag" value="--test-threads=1" />
+ </test>
+</configuration>
diff --git a/tests/vts/src/vts_libavf_test.rs b/tests/vts/src/vts_libavf_test.rs
new file mode 100644
index 0000000..ba38a2e
--- /dev/null
+++ b/tests/vts/src/vts_libavf_test.rs
@@ -0,0 +1,186 @@
+// Copyright 2024 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+//! Tests running a VM with LLNDK
+
+use anyhow::{bail, ensure, Context, Result};
+use log::info;
+use std::ffi::CStr;
+use std::fs::File;
+use std::io::{self, BufWriter, Write};
+use std::os::fd::IntoRawFd;
+use std::time::{Duration, Instant};
+use vsock::{VsockListener, VsockStream, VMADDR_CID_HOST};
+
+use avf_bindgen::*;
+use service_vm_comm::{Request, Response, ServiceVmRequest, VmType};
+
+const VM_MEMORY_MB: i32 = 16;
+const WRITE_BUFFER_CAPACITY: usize = 512;
+
+const LISTEN_TIMEOUT: Duration = Duration::from_secs(10);
+const READ_TIMEOUT: Duration = Duration::from_secs(10);
+const WRITE_TIMEOUT: Duration = Duration::from_secs(10);
+const STOP_TIMEOUT: timespec = timespec { tv_sec: 10, tv_nsec: 0 };
+
+/// Processes the request in the service VM.
+fn process_request(vsock_stream: &mut VsockStream, request: Request) -> Result<Response> {
+ write_request(vsock_stream, &ServiceVmRequest::Process(request))?;
+ read_response(vsock_stream)
+}
+
+/// Sends the request to the service VM.
+fn write_request(vsock_stream: &mut VsockStream, request: &ServiceVmRequest) -> Result<()> {
+ let mut buffer = BufWriter::with_capacity(WRITE_BUFFER_CAPACITY, vsock_stream);
+ ciborium::into_writer(request, &mut buffer)?;
+ buffer.flush().context("Failed to flush the buffer")?;
+ Ok(())
+}
+
+/// Reads the response from the service VM.
+fn read_response(vsock_stream: &mut VsockStream) -> Result<Response> {
+ let response: Response = ciborium::from_reader(vsock_stream)
+ .context("Failed to read the response from the service VM")?;
+ Ok(response)
+}
+
+fn listen_from_guest(port: u32) -> Result<VsockStream> {
+ let vsock_listener =
+ VsockListener::bind_with_cid_port(VMADDR_CID_HOST, port).context("Failed to bind vsock")?;
+ vsock_listener.set_nonblocking(true).context("Failed to set nonblocking")?;
+ let start_time = Instant::now();
+ loop {
+ if start_time.elapsed() >= LISTEN_TIMEOUT {
+ bail!("Timeout while listening");
+ }
+ match vsock_listener.accept() {
+ Ok((vsock_stream, _peer_addr)) => return Ok(vsock_stream),
+ Err(e) if e.kind() == io::ErrorKind::WouldBlock => {
+ std::thread::sleep(Duration::from_millis(100));
+ }
+ Err(e) => bail!("Failed to listen: {e:?}"),
+ }
+ }
+}
+
+fn run_rialto(protected_vm: bool) -> Result<()> {
+ let kernel_file =
+ File::open("/data/local/tmp/rialto.bin").context("Failed to open kernel file")?;
+ let kernel_fd = kernel_file.into_raw_fd();
+
+ // SAFETY: AVirtualMachineRawConfig_create() isn't unsafe but rust_bindgen forces it to be seen
+ // as unsafe
+ let config = unsafe { AVirtualMachineRawConfig_create() };
+
+ info!("raw config created");
+
+ // SAFETY: config is the only reference to a valid object
+ unsafe {
+ AVirtualMachineRawConfig_setName(
+ config,
+ CStr::from_bytes_with_nul(b"vts_libavf_test_rialto\0").unwrap().as_ptr(),
+ );
+ AVirtualMachineRawConfig_setKernel(config, kernel_fd);
+ AVirtualMachineRawConfig_setProtectedVm(config, protected_vm);
+ AVirtualMachineRawConfig_setMemoryMiB(config, VM_MEMORY_MB);
+ }
+
+ let mut vm = std::ptr::null_mut();
+ let mut service = std::ptr::null_mut();
+
+ ensure!(
+ // SAFETY: &mut service is a valid pointer to *AVirtualizationService
+ unsafe { AVirtualizationService_create(&mut service, false) } == 0,
+ "AVirtualizationService_create failed"
+ );
+
+ scopeguard::defer! {
+ // SAFETY: service is a valid pointer to AVirtualizationService
+ unsafe { AVirtualizationService_destroy(service); }
+ }
+
+ ensure!(
+ // SAFETY: &mut vm is a valid pointer to *AVirtualMachine
+ unsafe {
+ AVirtualMachine_createRaw(
+ service, config, -1, // console_in
+ -1, // console_out
+ -1, // log
+ &mut vm,
+ )
+ } == 0,
+ "AVirtualMachine_createRaw failed"
+ );
+
+ scopeguard::defer! {
+ // SAFETY: vm is a valid pointer to AVirtualMachine
+ unsafe { AVirtualMachine_destroy(vm); }
+ }
+
+ info!("vm created");
+
+ let vm_type = if protected_vm { VmType::ProtectedVm } else { VmType::NonProtectedVm };
+
+ let listener_thread = std::thread::spawn(move || listen_from_guest(vm_type.port()));
+
+ // SAFETY: vm is the only reference to a valid object
+ unsafe {
+ AVirtualMachine_start(vm);
+ }
+
+ info!("VM started");
+
+ let mut vsock_stream = listener_thread.join().unwrap()?;
+ vsock_stream.set_read_timeout(Some(READ_TIMEOUT))?;
+ vsock_stream.set_write_timeout(Some(WRITE_TIMEOUT))?;
+
+ info!("client connected");
+
+ let request_data = vec![1, 2, 3, 4, 5];
+ let expected_data = vec![5, 4, 3, 2, 1];
+ let response = process_request(&mut vsock_stream, Request::Reverse(request_data))
+ .context("Failed to process request")?;
+ let Response::Reverse(reversed_data) = response else {
+ bail!("Expected Response::Reverse but was {response:?}");
+ };
+ ensure!(reversed_data == expected_data, "Expected {expected_data:?} but was {reversed_data:?}");
+
+ info!("request processed");
+
+ write_request(&mut vsock_stream, &ServiceVmRequest::Shutdown)
+ .context("Failed to send shutdown")?;
+
+ info!("shutdown sent");
+
+ let mut stop_reason = AVirtualMachineStopReason::AVIRTUAL_MACHINE_UNRECOGNISED;
+ ensure!(
+ // SAFETY: vm is the only reference to a valid object
+ unsafe { AVirtualMachine_waitForStop(vm, &STOP_TIMEOUT, &mut stop_reason) },
+ "AVirtualMachine_waitForStop failed"
+ );
+
+ info!("stopped");
+
+ Ok(())
+}
+
+#[test]
+fn test_run_rialto_protected() -> Result<()> {
+ run_rialto(true /* protected_vm */)
+}
+
+#[test]
+fn test_run_rialto_non_protected() -> Result<()> {
+ run_rialto(false /* protected_vm */)
+}