1 // Copyright 2025 The Android Open Source Project
2 //
3 // Licensed under the Apache License, Version 2.0 (the "License");
4 // you may not use this file except in compliance with the License.
5 // You may obtain a copy of the License at
6 //
7 // http://www.apache.org/licenses/LICENSE-2.0
8 //
9 // Unless required by applicable law or agreed to in writing, software
10 // distributed under the License is distributed on an "AS IS" BASIS,
11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 // See the License for the specific language governing permissions and
13 // limitations under the License.
14
15 //! Tests running a VM with older images
16
17 use anyhow::{bail, ensure, Context, Result};
18 use log::info;
19 use std::ffi::CStr;
20 use std::fs::File;
21 use std::io::{self, BufWriter, Write};
22 use std::os::fd::IntoRawFd;
23 use std::time::{Duration, Instant};
24 use vsock::{VsockListener, VsockStream, VMADDR_CID_HOST};
25
26 use avf_bindgen::*;
27 use service_vm_comm::{Request, Response, ServiceVmRequest, VmType};
28
29 const VM_MEMORY_MB: i32 = 16;
30 const WRITE_BUFFER_CAPACITY: usize = 512;
31
32 const LISTEN_TIMEOUT: Duration = Duration::from_secs(10);
33 const READ_TIMEOUT: Duration = Duration::from_secs(10);
34 const WRITE_TIMEOUT: Duration = Duration::from_secs(10);
35 const STOP_TIMEOUT: timespec = timespec { tv_sec: 10, tv_nsec: 0 };
36
37 /// Processes the request in the service VM.
process_request(vsock_stream: &mut VsockStream, request: Request) -> Result<Response>38 fn process_request(vsock_stream: &mut VsockStream, request: Request) -> Result<Response> {
39 write_request(vsock_stream, &ServiceVmRequest::Process(request))?;
40 read_response(vsock_stream)
41 }
42
43 /// Sends the request to the service VM.
write_request(vsock_stream: &mut VsockStream, request: &ServiceVmRequest) -> Result<()>44 fn write_request(vsock_stream: &mut VsockStream, request: &ServiceVmRequest) -> Result<()> {
45 let mut buffer = BufWriter::with_capacity(WRITE_BUFFER_CAPACITY, vsock_stream);
46 ciborium::into_writer(request, &mut buffer)?;
47 buffer.flush().context("Failed to flush the buffer")?;
48 Ok(())
49 }
50
51 /// Reads the response from the service VM.
read_response(vsock_stream: &mut VsockStream) -> Result<Response>52 fn read_response(vsock_stream: &mut VsockStream) -> Result<Response> {
53 let response: Response = ciborium::from_reader(vsock_stream)
54 .context("Failed to read the response from the service VM")?;
55 Ok(response)
56 }
57
listen_from_guest(port: u32) -> Result<VsockStream>58 fn listen_from_guest(port: u32) -> Result<VsockStream> {
59 let vsock_listener =
60 VsockListener::bind_with_cid_port(VMADDR_CID_HOST, port).context("Failed to bind vsock")?;
61 vsock_listener.set_nonblocking(true).context("Failed to set nonblocking")?;
62 let start_time = Instant::now();
63 loop {
64 if start_time.elapsed() >= LISTEN_TIMEOUT {
65 bail!("Timeout while listening");
66 }
67 match vsock_listener.accept() {
68 Ok((vsock_stream, _peer_addr)) => return Ok(vsock_stream),
69 Err(e) if e.kind() == io::ErrorKind::WouldBlock => {
70 std::thread::sleep(Duration::from_millis(100));
71 }
72 Err(e) => bail!("Failed to listen: {e:?}"),
73 }
74 }
75 }
76
run_vm(image_path: &str, test_name: &CStr, protected_vm: bool) -> Result<()>77 fn run_vm(image_path: &str, test_name: &CStr, protected_vm: bool) -> Result<()> {
78 let kernel_file = File::open(image_path).context("Failed to open kernel file")?;
79 let kernel_fd = kernel_file.into_raw_fd();
80
81 // SAFETY: AVirtualMachineRawConfig_create() isn't unsafe but rust_bindgen forces it to be seen
82 // as unsafe
83 let config = unsafe { AVirtualMachineRawConfig_create() };
84
85 info!("raw config created");
86
87 // SAFETY: config is the only reference to a valid object
88 unsafe {
89 AVirtualMachineRawConfig_setName(config, test_name.as_ptr());
90 AVirtualMachineRawConfig_setKernel(config, kernel_fd);
91 AVirtualMachineRawConfig_setProtectedVm(config, protected_vm);
92 AVirtualMachineRawConfig_setMemoryMiB(config, VM_MEMORY_MB);
93 }
94
95 let mut vm = std::ptr::null_mut();
96 let mut service = std::ptr::null_mut();
97
98 ensure!(
99 // SAFETY: &mut service is a valid pointer to *AVirtualizationService
100 unsafe { AVirtualizationService_create(&mut service, false) } == 0,
101 "AVirtualizationService_create failed"
102 );
103
104 scopeguard::defer! {
105 // SAFETY: service is a valid pointer to AVirtualizationService
106 unsafe { AVirtualizationService_destroy(service); }
107 }
108
109 ensure!(
110 // SAFETY: &mut vm is a valid pointer to *AVirtualMachine
111 unsafe {
112 AVirtualMachine_createRaw(
113 service, config, -1, // console_in
114 -1, // console_out
115 -1, // log
116 &mut vm,
117 )
118 } == 0,
119 "AVirtualMachine_createRaw failed"
120 );
121
122 scopeguard::defer! {
123 // SAFETY: vm is a valid pointer to AVirtualMachine
124 unsafe { AVirtualMachine_destroy(vm); }
125 }
126
127 info!("vm created");
128
129 let vm_type = if protected_vm { VmType::ProtectedVm } else { VmType::NonProtectedVm };
130
131 let listener_thread = std::thread::spawn(move || listen_from_guest(vm_type.port()));
132
133 // SAFETY: vm is the only reference to a valid object
134 unsafe {
135 AVirtualMachine_start(vm);
136 }
137
138 info!("VM started");
139
140 let mut vsock_stream = listener_thread.join().unwrap()?;
141 vsock_stream.set_read_timeout(Some(READ_TIMEOUT))?;
142 vsock_stream.set_write_timeout(Some(WRITE_TIMEOUT))?;
143
144 info!("client connected");
145
146 let request_data = vec![1, 2, 3, 4, 5];
147 let expected_data = vec![5, 4, 3, 2, 1];
148 let response = process_request(&mut vsock_stream, Request::Reverse(request_data))
149 .context("Failed to process request")?;
150 let Response::Reverse(reversed_data) = response else {
151 bail!("Expected Response::Reverse but was {response:?}");
152 };
153 ensure!(reversed_data == expected_data, "Expected {expected_data:?} but was {reversed_data:?}");
154
155 info!("request processed");
156
157 write_request(&mut vsock_stream, &ServiceVmRequest::Shutdown)
158 .context("Failed to send shutdown")?;
159
160 info!("shutdown sent");
161
162 let mut stop_reason = AVirtualMachineStopReason::AVIRTUAL_MACHINE_UNRECOGNISED;
163 ensure!(
164 // SAFETY: vm is the only reference to a valid object
165 unsafe { AVirtualMachine_waitForStop(vm, &STOP_TIMEOUT, &mut stop_reason) },
166 "AVirtualMachine_waitForStop failed"
167 );
168 assert_eq!(stop_reason, AVirtualMachineStopReason::AVIRTUAL_MACHINE_SHUTDOWN);
169
170 info!("stopped");
171
172 Ok(())
173 }
174
175 #[test]
test_run_rialto_non_protected() -> Result<()>176 fn test_run_rialto_non_protected() -> Result<()> {
177 if hypervisor_props::is_vm_supported()? {
178 run_vm(
179 "/data/local/tmp/rialto.bin", /* image_path */
180 c"test_rialto", /* test_name */
181 false, /* protected_vm */
182 )
183 } else {
184 info!("VMs are not supported on device. skipping test");
185 Ok(())
186 }
187 }
188
189 #[test]
test_run_android16_rialto_non_protected() -> Result<()>190 fn test_run_android16_rialto_non_protected() -> Result<()> {
191 if hypervisor_props::is_vm_supported()? {
192 run_vm(
193 "/data/local/tmp/android16_rialto.bin", /* image_path */
194 c"android16_test_rialto", /* test_name */
195 false, /* protected_vm */
196 )
197 } else {
198 info!("VMs are not supported on device. skipping test");
199 Ok(())
200 }
201 }
202