Create a Rust wrapper for vm_payload

And use it in place of directly calling the bindgen-generated
interface in our current clients.

Bug: 340857915
Test: atest VmAttestationTestApp
  composd_cmd test-compile
Change-Id: I51f1c1ab6a4dce09d9160731aacd83ebb9c0ce07
diff --git a/compos/Android.bp b/compos/Android.bp
index b840506..220533a 100644
--- a/compos/Android.bp
+++ b/compos/Android.bp
@@ -25,7 +25,7 @@
         "librpcbinder_rs",
         "librustutils",
         "libscopeguard",
-        "libvm_payload_bindgen",
+        "libvm_payload_rs",
     ],
     prefer_rlib: true,
     shared_libs: [
diff --git a/compos/src/compsvc_main.rs b/compos/src/compsvc_main.rs
index 06cc599..9bc522c 100644
--- a/compos/src/compsvc_main.rs
+++ b/compos/src/compsvc_main.rs
@@ -23,13 +23,9 @@
 mod fsverity;
 
 use anyhow::Result;
-use binder::unstable_api::AsNative;
 use compos_common::COMPOS_VSOCK_PORT;
 use log::{debug, error};
-use std::os::raw::c_void;
 use std::panic;
-use std::ptr;
-use vm_payload_bindgen::{AIBinder, AVmPayload_notifyPayloadReady, AVmPayload_runVsockRpcServer};
 
 fn main() {
     if let Err(e) = try_main() {
@@ -50,17 +46,5 @@
     }));
 
     debug!("compsvc is starting as a rpc service.");
-    let param = ptr::null_mut();
-    let mut service = compsvc::new_binder()?.as_binder();
-    let service = service.as_native_mut() as *mut AIBinder;
-    // SAFETY: We hold a strong pointer, so the raw pointer remains valid. The bindgen AIBinder
-    // is the same type as sys::AIBinder. It is safe for on_ready to be invoked at any time, with
-    // any parameter.
-    unsafe { AVmPayload_runVsockRpcServer(service, COMPOS_VSOCK_PORT, Some(on_ready), param) }
-}
-
-extern "C" fn on_ready(_param: *mut c_void) {
-    // SAFETY: Invokes a method from the bindgen library `vm_payload_bindgen` which is safe to
-    // call at any time.
-    unsafe { AVmPayload_notifyPayloadReady() };
+    vm_payload::run_single_vsock_service(compsvc::new_binder()?, COMPOS_VSOCK_PORT)
 }
diff --git a/service_vm/demo_apk/Android.bp b/service_vm/demo_apk/Android.bp
index 3750fe6..c64b70a 100644
--- a/service_vm/demo_apk/Android.bp
+++ b/service_vm/demo_apk/Android.bp
@@ -23,7 +23,7 @@
         "libandroid_logger",
         "libanyhow",
         "liblog_rust",
-        "libvm_payload_bindgen",
+        "libvm_payload_rs",
     ],
 }
 
diff --git a/service_vm/demo_apk/src/main.rs b/service_vm/demo_apk/src/main.rs
index 8ea4e65..26df52c 100644
--- a/service_vm/demo_apk/src/main.rs
+++ b/service_vm/demo_apk/src/main.rs
@@ -14,25 +14,15 @@
 
 //! Main executable of Service VM client for manual testing.
 
-use anyhow::{anyhow, ensure, Result};
+use anyhow::{ensure, Context, Result};
 use log::{error, info};
-use std::{
-    ffi::{c_void, CStr},
-    panic,
-    ptr::{self, NonNull},
-    result,
-};
-use vm_payload_bindgen::{
-    AVmAttestationResult, AVmAttestationResult_free, AVmAttestationResult_getCertificateAt,
-    AVmAttestationResult_getCertificateCount, AVmAttestationResult_getPrivateKey,
-    AVmAttestationResult_sign, AVmAttestationStatus, AVmAttestationStatus_toString,
-    AVmPayload_requestAttestation,
-};
+use std::panic;
+use vm_payload::AttestationError;
+
+vm_payload::main!(main);
 
 /// Entry point of the Service VM client.
-#[allow(non_snake_case)]
-#[no_mangle]
-pub extern "C" fn AVmPayload_main() {
+fn main() {
     android_logger::init_once(
         android_logger::Config::default()
             .with_tag("service_vm_client")
@@ -52,15 +42,11 @@
     info!("Welcome to Service VM Client!");
 
     let too_big_challenge = &[0u8; 66];
-    let res = AttestationResult::request_attestation(too_big_challenge);
+    let res = vm_payload::request_attestation(too_big_challenge);
     ensure!(res.is_err());
-    let status = res.unwrap_err();
-    ensure!(
-        status == AVmAttestationStatus::ATTESTATION_ERROR_INVALID_CHALLENGE,
-        "Unexpected status: {:?}",
-        status
-    );
-    info!("Status: {:?}", status_to_cstr(status));
+    let error = res.unwrap_err();
+    ensure!(error == AttestationError::InvalidChallenge, "Unexpected error: {error:?}");
+    info!("Error: {error}");
 
     // The data below is only a placeholder generated randomly with urandom
     let challenge = &[
@@ -68,162 +54,18 @@
         0x67, 0xc3, 0x3e, 0x73, 0x9b, 0x30, 0xbd, 0x04, 0x20, 0x2e, 0xde, 0x3b, 0x1d, 0xc8, 0x07,
         0x11, 0x7b,
     ];
-    let res = AttestationResult::request_attestation(challenge)
-        .map_err(|e| anyhow!("Unexpected status: {:?}", status_to_cstr(e)))?;
+    let res = vm_payload::request_attestation(challenge).context("Unexpected attestation error")?;
 
-    let cert_chain = res.certificate_chain()?;
+    let cert_chain: Vec<_> = res.certificate_chain().collect();
     info!("Attestation result certificateChain = {:?}", cert_chain);
 
-    let private_key = res.private_key()?;
+    let private_key = res.private_key();
     info!("Attestation result privateKey = {:?}", private_key);
 
     let message = b"Hello from Service VM client";
     info!("Signing message: {:?}", message);
-    let signature = res.sign(message)?;
+    let signature = res.sign_message(message);
     info!("Signature: {:?}", signature);
 
     Ok(())
 }
-
-#[derive(Debug)]
-struct AttestationResult(NonNull<AVmAttestationResult>);
-
-impl AttestationResult {
-    fn request_attestation(challenge: &[u8]) -> result::Result<Self, AVmAttestationStatus> {
-        let mut res: *mut AVmAttestationResult = ptr::null_mut();
-        // SAFETY: It is safe as we only read the challenge within its bounds and the
-        // function does not retain any reference to it.
-        let status = unsafe {
-            AVmPayload_requestAttestation(
-                challenge.as_ptr() as *const c_void,
-                challenge.len(),
-                &mut res,
-            )
-        };
-        if status == AVmAttestationStatus::ATTESTATION_OK {
-            info!("Attestation succeeds. Status: {:?}", status_to_cstr(status));
-            let res = NonNull::new(res).expect("The attestation result is null");
-            Ok(Self(res))
-        } else {
-            Err(status)
-        }
-    }
-
-    fn certificate_chain(&self) -> Result<Vec<Box<[u8]>>> {
-        let num_certs = get_certificate_count(self.as_ref());
-        let mut certs = Vec::with_capacity(num_certs);
-        for i in 0..num_certs {
-            certs.push(get_certificate_at(self.as_ref(), i)?);
-        }
-        Ok(certs)
-    }
-
-    fn private_key(&self) -> Result<Box<[u8]>> {
-        get_private_key(self.as_ref())
-    }
-
-    fn sign(&self, message: &[u8]) -> Result<Box<[u8]>> {
-        sign_with_attested_key(self.as_ref(), message)
-    }
-}
-
-impl AsRef<AVmAttestationResult> for AttestationResult {
-    fn as_ref(&self) -> &AVmAttestationResult {
-        // SAFETY: This field is private, and only populated with a successful call to
-        // `AVmPayload_requestAttestation`.
-        unsafe { self.0.as_ref() }
-    }
-}
-
-impl Drop for AttestationResult {
-    fn drop(&mut self) {
-        // SAFETY: This field is private, and only populated with a successful call to
-        // `AVmPayload_requestAttestation`, and not freed elsewhere.
-        unsafe { AVmAttestationResult_free(self.0.as_ptr()) };
-    }
-}
-
-fn get_certificate_count(res: &AVmAttestationResult) -> usize {
-    // SAFETY: The result is returned by `AVmPayload_requestAttestation` and should be valid
-    // before getting freed.
-    unsafe { AVmAttestationResult_getCertificateCount(res) }
-}
-
-fn get_certificate_at(res: &AVmAttestationResult, index: usize) -> Result<Box<[u8]>> {
-    let size =
-        // SAFETY: The result is returned by `AVmPayload_requestAttestation` and should be valid
-        // before getting freed.
-        unsafe { AVmAttestationResult_getCertificateAt(res, index, ptr::null_mut(), 0) };
-    let mut cert = vec![0u8; size];
-    // SAFETY: The result is returned by `AVmPayload_requestAttestation` and should be valid
-    // before getting freed. This function only writes within the bounds of `cert`.
-    // And `cert` cannot overlap `res` because we just allocated it.
-    let size = unsafe {
-        AVmAttestationResult_getCertificateAt(
-            res,
-            index,
-            cert.as_mut_ptr() as *mut c_void,
-            cert.len(),
-        )
-    };
-    ensure!(size == cert.len());
-    Ok(cert.into_boxed_slice())
-}
-
-fn get_private_key(res: &AVmAttestationResult) -> Result<Box<[u8]>> {
-    let size =
-        // SAFETY: The result is returned by `AVmPayload_requestAttestation` and should be valid
-        // before getting freed.
-        unsafe { AVmAttestationResult_getPrivateKey(res, ptr::null_mut(), 0) };
-    let mut private_key = vec![0u8; size];
-    // SAFETY: The result is returned by `AVmPayload_requestAttestation` and should be valid
-    // before getting freed. This function only writes within the bounds of `private_key`.
-    // And `private_key` cannot overlap `res` because we just allocated it.
-    let size = unsafe {
-        AVmAttestationResult_getPrivateKey(
-            res,
-            private_key.as_mut_ptr() as *mut c_void,
-            private_key.len(),
-        )
-    };
-    ensure!(size == private_key.len());
-    Ok(private_key.into_boxed_slice())
-}
-
-fn sign_with_attested_key(res: &AVmAttestationResult, message: &[u8]) -> Result<Box<[u8]>> {
-    // SAFETY: The result is returned by `AVmPayload_requestAttestation` and should be valid
-    // before getting freed.
-    let size = unsafe {
-        AVmAttestationResult_sign(
-            res,
-            message.as_ptr() as *const c_void,
-            message.len(),
-            ptr::null_mut(),
-            0,
-        )
-    };
-    let mut signature = vec![0u8; size];
-    // SAFETY: The result is returned by `AVmPayload_requestAttestation` and should be valid
-    // before getting freed. This function only writes within the bounds of `signature`.
-    // And `signature` cannot overlap `res` because we just allocated it.
-    let size = unsafe {
-        AVmAttestationResult_sign(
-            res,
-            message.as_ptr() as *const c_void,
-            message.len(),
-            signature.as_mut_ptr() as *mut c_void,
-            signature.len(),
-        )
-    };
-    ensure!(size == signature.len());
-    Ok(signature.into_boxed_slice())
-}
-
-fn status_to_cstr(status: AVmAttestationStatus) -> &'static CStr {
-    // SAFETY: The function only reads the given enum status and returns a pointer to a
-    // static string.
-    let message = unsafe { AVmAttestationStatus_toString(status) };
-    // SAFETY: The pointer returned by `AVmAttestationStatus_toString` is guaranteed to
-    // point to a valid C String that lives forever.
-    unsafe { CStr::from_ptr(message) }
-}
diff --git a/service_vm/test_apk/Android.bp b/service_vm/test_apk/Android.bp
index 1ba156f..58b394a 100644
--- a/service_vm/test_apk/Android.bp
+++ b/service_vm/test_apk/Android.bp
@@ -39,7 +39,7 @@
         "libanyhow",
         "libavflog",
         "liblog_rust",
-        "libvm_payload_bindgen",
+        "libvm_payload_rs",
     ],
 }
 
diff --git a/service_vm/test_apk/src/native/main.rs b/service_vm/test_apk/src/native/main.rs
index 00ddff8..5f5dc1c 100644
--- a/service_vm/test_apk/src/native/main.rs
+++ b/service_vm/test_apk/src/native/main.rs
@@ -14,35 +14,26 @@
 
 //! Main executable of VM attestation for end-to-end testing.
 
-use anyhow::{anyhow, ensure, Result};
+use anyhow::Result;
 use avflog::LogResult;
 use com_android_virt_vm_attestation_testservice::{
     aidl::com::android::virt::vm_attestation::testservice::IAttestationService::{
         AttestationStatus::AttestationStatus, BnAttestationService, IAttestationService,
         SigningResult::SigningResult, PORT,
     },
-    binder::{self, unstable_api::AsNative, BinderFeatures, Interface, IntoBinderResult, Strong},
+    binder::{self, BinderFeatures, Interface, IntoBinderResult, Strong},
 };
 use log::{error, info};
 use std::{
-    ffi::{c_void, CStr},
     panic,
-    ptr::{self, NonNull},
-    result,
     sync::{Arc, Mutex},
 };
-use vm_payload_bindgen::{
-    AIBinder, AVmAttestationResult, AVmAttestationResult_free,
-    AVmAttestationResult_getCertificateAt, AVmAttestationResult_getCertificateCount,
-    AVmAttestationResult_getPrivateKey, AVmAttestationResult_sign, AVmAttestationStatus,
-    AVmAttestationStatus_toString, AVmPayload_notifyPayloadReady, AVmPayload_requestAttestation,
-    AVmPayload_requestAttestationForTesting, AVmPayload_runVsockRpcServer,
-};
+use vm_payload::{AttestationError, AttestationResult};
 
-/// Entry point of the Service VM client.
-#[allow(non_snake_case)]
-#[no_mangle]
-pub extern "C" fn AVmPayload_main() {
+vm_payload::main!(main);
+
+// Entry point of the Service VM client.
+fn main() {
     android_logger::init_once(
         android_logger::Config::default()
             .with_tag("service_vm_client")
@@ -61,18 +52,7 @@
 fn try_main() -> Result<()> {
     info!("Welcome to Service VM Client!");
 
-    let mut service = AttestationService::new_binder().as_binder();
-    let service = service.as_native_mut() as *mut AIBinder;
-    let param = ptr::null_mut();
-    // SAFETY: We hold a strong pointer, so the raw pointer remains valid. The bindgen AIBinder
-    // is the same type as `sys::AIBinder`. It is safe for `on_ready` to be invoked at any time,
-    // with any parameter.
-    unsafe { AVmPayload_runVsockRpcServer(service, PORT.try_into()?, Some(on_ready), param) };
-}
-
-extern "C" fn on_ready(_param: *mut c_void) {
-    // SAFETY: It is safe to call `AVmPayload_notifyPayloadReady` at any time.
-    unsafe { AVmPayload_notifyPayloadReady() };
+    vm_payload::run_single_vsock_service(AttestationService::new_binder(), PORT.try_into()?)
 }
 
 struct AttestationService {
@@ -88,11 +68,11 @@
     }
 }
 
+#[allow(non_snake_case)]
 impl IAttestationService for AttestationService {
     fn requestAttestationForTesting(&self) -> binder::Result<()> {
         const CHALLENGE: &[u8] = &[0xaa; 32];
-        let res = AttestationResult::request_attestation_for_testing(CHALLENGE)
-            .map_err(|e| anyhow!("Unexpected status: {:?}", status_to_cstr(e)))
+        let res = vm_payload::request_attestation_for_testing(CHALLENGE)
             .with_log()
             .or_service_specific_exception(-1)?;
         *self.res.lock().unwrap() = Some(res);
@@ -104,218 +84,46 @@
         challenge: &[u8],
         message: &[u8],
     ) -> binder::Result<SigningResult> {
-        let res = match AttestationResult::request_attestation(challenge) {
+        let res: AttestationResult = match vm_payload::request_attestation(challenge) {
             Ok(res) => res,
-            Err(status) => {
-                let status = to_attestation_status(status);
+            Err(e) => {
+                let status = to_attestation_status(e);
                 return Ok(SigningResult { certificateChain: vec![], signature: vec![], status });
             }
         };
-        let certificate_chain =
-            res.certificate_chain().with_log().or_service_specific_exception(-1)?;
+
+        let certificate_chain: Vec<u8> = res.certificate_chain().flatten().collect();
         let status = AttestationStatus::OK;
-        let signature = res.sign(message).with_log().or_service_specific_exception(-1)?;
+        let signature = res.sign_message(message);
+
         Ok(SigningResult { certificateChain: certificate_chain, signature, status })
     }
 
     fn validateAttestationResult(&self) -> binder::Result<()> {
         // TODO(b/191073073): Returns the attestation result to the host for validation.
-        self.res.lock().unwrap().as_ref().unwrap().log().or_service_specific_exception(-1)
-    }
-}
-
-fn to_attestation_status(status: AVmAttestationStatus) -> AttestationStatus {
-    match status {
-        AVmAttestationStatus::ATTESTATION_OK => AttestationStatus::OK,
-        AVmAttestationStatus::ATTESTATION_ERROR_INVALID_CHALLENGE => {
-            AttestationStatus::ERROR_INVALID_CHALLENGE
-        }
-        AVmAttestationStatus::ATTESTATION_ERROR_ATTESTATION_FAILED => {
-            AttestationStatus::ERROR_ATTESTATION_FAILED
-        }
-        AVmAttestationStatus::ATTESTATION_ERROR_UNSUPPORTED => AttestationStatus::ERROR_UNSUPPORTED,
-    }
-}
-
-#[derive(Debug)]
-struct AttestationResult(NonNull<AVmAttestationResult>);
-
-// Safety: `AttestationResult` is not `Send` because it contains a raw pointer to a C struct.
-unsafe impl Send for AttestationResult {}
-
-impl AttestationResult {
-    fn request_attestation_for_testing(
-        challenge: &[u8],
-    ) -> result::Result<Self, AVmAttestationStatus> {
-        let mut res: *mut AVmAttestationResult = ptr::null_mut();
-        // SAFETY: It is safe as we only read the challenge within its bounds and the
-        // function does not retain any reference to it.
-        let status = unsafe {
-            AVmPayload_requestAttestationForTesting(
-                challenge.as_ptr() as *const c_void,
-                challenge.len(),
-                &mut res,
-            )
-        };
-        if status == AVmAttestationStatus::ATTESTATION_OK {
-            info!("Attestation succeeds. Status: {:?}", status_to_cstr(status));
-            let res = NonNull::new(res).expect("The attestation result is null");
-            Ok(Self(res))
-        } else {
-            Err(status)
-        }
-    }
-
-    fn request_attestation(challenge: &[u8]) -> result::Result<Self, AVmAttestationStatus> {
-        let mut res: *mut AVmAttestationResult = ptr::null_mut();
-        // SAFETY: It is safe as we only read the challenge within its bounds and the
-        // function does not retain any reference to it.
-        let status = unsafe {
-            AVmPayload_requestAttestation(
-                challenge.as_ptr() as *const c_void,
-                challenge.len(),
-                &mut res,
-            )
-        };
-        if status == AVmAttestationStatus::ATTESTATION_OK {
-            info!("Attestation succeeds. Status: {:?}", status_to_cstr(status));
-            let res = NonNull::new(res).expect("The attestation result is null");
-            Ok(Self(res))
-        } else {
-            Err(status)
-        }
-    }
-
-    fn certificate_chain(&self) -> Result<Vec<u8>> {
-        let num_certs = get_certificate_count(self.as_ref());
-        let mut certs = Vec::new();
-        for i in 0..num_certs {
-            certs.extend(get_certificate_at(self.as_ref(), i)?.iter());
-        }
-        Ok(certs)
-    }
-
-    fn private_key(&self) -> Result<Box<[u8]>> {
-        get_private_key(self.as_ref())
-    }
-
-    fn sign(&self, message: &[u8]) -> Result<Vec<u8>> {
-        sign_with_attested_key(self.as_ref(), message)
-    }
-
-    fn log(&self) -> Result<()> {
-        let cert_chain = self.certificate_chain()?;
-        info!("Attestation result certificateChain = {:?}", cert_chain);
-
-        let private_key = self.private_key()?;
-        info!("Attestation result privateKey = {:?}", private_key);
-
-        let message = b"Hello from Service VM client";
-        info!("Signing message: {:?}", message);
-        let signature = self.sign(message)?;
-        info!("Signature: {:?}", signature);
+        log(self.res.lock().unwrap().as_ref().unwrap());
         Ok(())
     }
 }
 
-impl AsRef<AVmAttestationResult> for AttestationResult {
-    fn as_ref(&self) -> &AVmAttestationResult {
-        // SAFETY: This field is private, and only populated with a successful call to
-        // `AVmPayload_requestAttestation`.
-        unsafe { self.0.as_ref() }
+fn log(res: &AttestationResult) {
+    for (i, cert) in res.certificate_chain().enumerate() {
+        info!("Attestation result certificate {i} = {cert:?}");
     }
+
+    let private_key = res.private_key();
+    info!("Attestation result privateKey = {private_key:?}");
+
+    let message = b"Hello from Service VM client";
+    info!("Signing message: {message:?}");
+    let signature = res.sign_message(message);
+    info!("Signature: {signature:?}");
 }
 
-impl Drop for AttestationResult {
-    fn drop(&mut self) {
-        // SAFETY: This field is private, and only populated with a successful call to
-        // `AVmPayload_requestAttestation`, and not freed elsewhere.
-        unsafe { AVmAttestationResult_free(self.0.as_ptr()) };
+fn to_attestation_status(e: AttestationError) -> AttestationStatus {
+    match e {
+        AttestationError::InvalidChallenge => AttestationStatus::ERROR_INVALID_CHALLENGE,
+        AttestationError::AttestationFailed => AttestationStatus::ERROR_ATTESTATION_FAILED,
+        AttestationError::AttestationUnsupported => AttestationStatus::ERROR_UNSUPPORTED,
     }
 }
-
-fn get_certificate_count(res: &AVmAttestationResult) -> usize {
-    // SAFETY: The result is returned by `AVmPayload_requestAttestation` and should be valid
-    // before getting freed.
-    unsafe { AVmAttestationResult_getCertificateCount(res) }
-}
-
-fn get_certificate_at(res: &AVmAttestationResult, index: usize) -> Result<Box<[u8]>> {
-    let size =
-        // SAFETY: The result is returned by `AVmPayload_requestAttestation` and should be valid
-        // before getting freed.
-        unsafe { AVmAttestationResult_getCertificateAt(res, index, ptr::null_mut(), 0) };
-    let mut cert = vec![0u8; size];
-    // SAFETY: The result is returned by `AVmPayload_requestAttestation` and should be valid
-    // before getting freed. This function only writes within the bounds of `cert`.
-    // And `cert` cannot overlap `res` because we just allocated it.
-    let size = unsafe {
-        AVmAttestationResult_getCertificateAt(
-            res,
-            index,
-            cert.as_mut_ptr() as *mut c_void,
-            cert.len(),
-        )
-    };
-    ensure!(size == cert.len());
-    Ok(cert.into_boxed_slice())
-}
-
-fn get_private_key(res: &AVmAttestationResult) -> Result<Box<[u8]>> {
-    let size =
-        // SAFETY: The result is returned by `AVmPayload_requestAttestation` and should be valid
-        // before getting freed.
-        unsafe { AVmAttestationResult_getPrivateKey(res, ptr::null_mut(), 0) };
-    let mut private_key = vec![0u8; size];
-    // SAFETY: The result is returned by `AVmPayload_requestAttestation` and should be valid
-    // before getting freed. This function only writes within the bounds of `private_key`.
-    // And `private_key` cannot overlap `res` because we just allocated it.
-    let size = unsafe {
-        AVmAttestationResult_getPrivateKey(
-            res,
-            private_key.as_mut_ptr() as *mut c_void,
-            private_key.len(),
-        )
-    };
-    ensure!(size == private_key.len());
-    Ok(private_key.into_boxed_slice())
-}
-
-fn sign_with_attested_key(res: &AVmAttestationResult, message: &[u8]) -> Result<Vec<u8>> {
-    // SAFETY: The result is returned by `AVmPayload_requestAttestation` and should be valid
-    // before getting freed.
-    let size = unsafe {
-        AVmAttestationResult_sign(
-            res,
-            message.as_ptr() as *const c_void,
-            message.len(),
-            ptr::null_mut(),
-            0,
-        )
-    };
-    let mut signature = vec![0u8; size];
-    // SAFETY: The result is returned by `AVmPayload_requestAttestation` and should be valid
-    // before getting freed. This function only writes within the bounds of `signature`.
-    // And `signature` cannot overlap `res` because we just allocated it.
-    let size = unsafe {
-        AVmAttestationResult_sign(
-            res,
-            message.as_ptr() as *const c_void,
-            message.len(),
-            signature.as_mut_ptr() as *mut c_void,
-            signature.len(),
-        )
-    };
-    ensure!(size <= signature.len());
-    signature.truncate(size);
-    Ok(signature)
-}
-
-fn status_to_cstr(status: AVmAttestationStatus) -> &'static CStr {
-    // SAFETY: The function only reads the given enum status and returns a pointer to a
-    // static string.
-    let message = unsafe { AVmAttestationStatus_toString(status) };
-    // SAFETY: The pointer returned by `AVmAttestationStatus_toString` is guaranteed to
-    // point to a valid C String that lives forever.
-    unsafe { CStr::from_ptr(message) }
-}
diff --git a/vm_payload/Android.bp b/vm_payload/Android.bp
index 229f533..cf2a002 100644
--- a/vm_payload/Android.bp
+++ b/vm_payload/Android.bp
@@ -39,8 +39,8 @@
     visibility: [":__subpackages__"],
 }
 
-// Rust wrappers round the C API for Rust clients.
-// (Yes, this involves going Rust -> C -> Rust.)
+// Access to the C API for Rust code.
+// This shouldn't be used directly - prefer libvm_payload_rs (below)
 rust_bindgen {
     name: "libvm_payload_bindgen",
     wrapper_src: "include-restricted/vm_payload_restricted.h",
@@ -51,15 +51,31 @@
     bindgen_flags: [
         "--default-enum-style rust",
     ],
-    visibility: [
-        "//packages/modules/Virtualization/compos",
-        "//packages/modules/Virtualization/service_vm:__subpackages__",
-    ],
     shared_libs: [
         "libvm_payload#current",
     ],
 }
 
+// Wrapper library for the raw C API for use by Rust clients.
+// (Yes, this involves going Rust -> C -> Rust.)
+// This is not a stable API - we may change it in subsequent versions.
+// But it is made available as an rlib so it is linked into any
+// code using it, leaving only dependencies on stable APIs.
+// So code built with it should run unchanged on future versions.
+rust_library_rlib {
+    name: "libvm_payload_rs",
+    crate_name: "vm_payload",
+    defaults: ["avf_build_flags_rust"],
+    srcs: ["wrapper/lib.rs"],
+    rustlibs: [
+        "libbinder_rs",
+        "libstatic_assertions",
+        "libvm_payload_bindgen",
+    ],
+    apex_available: ["com.android.compos"],
+    visibility: ["//visibility:public"],
+}
+
 // Shared library for clients to link against.
 cc_library_shared {
     name: "libvm_payload",
diff --git a/vm_payload/README.md b/vm_payload/README.md
index 4b1e6f3..66fd532 100644
--- a/vm_payload/README.md
+++ b/vm_payload/README.md
@@ -70,3 +70,16 @@
 See [AIDL
 backends](https://source.android.com/docs/core/architecture/aidl/aidl-backends)
 for information on using AIDL with the NDK Binder from C++.
+
+## Rust
+
+A Rust wrapper library for the VM Payload API is available (as an rlib) for VM
+payloads written in Rust.
+
+This wrapper is not guaranteed to be stable; we may change it in future
+versions. But payload code built using it will depend only on the C VM Payload
+API and the NDK APIs that are available to the payload, so should run unchanged
+on future versions.
+
+See [wrapper/lib.rs](wrapper/lib.rs) and `libvm_payload_rs` in
+[Android.bp](Android.bp).
diff --git a/vm_payload/wrapper/attestation.rs b/vm_payload/wrapper/attestation.rs
new file mode 100644
index 0000000..e0055d5
--- /dev/null
+++ b/vm_payload/wrapper/attestation.rs
@@ -0,0 +1,288 @@
+/*
+ * 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.
+ */
+
+use std::error::Error;
+use std::ffi::{c_void, CStr};
+use std::fmt::{self, Display};
+use std::iter::FusedIterator;
+use std::ptr::{self, NonNull};
+
+use vm_payload_bindgen::{
+    AVmAttestationResult, AVmAttestationResult_free, AVmAttestationResult_getCertificateAt,
+    AVmAttestationResult_getCertificateCount, AVmAttestationResult_getPrivateKey,
+    AVmAttestationResult_sign, AVmAttestationStatus, AVmAttestationStatus_toString,
+    AVmPayload_requestAttestation, AVmPayload_requestAttestationForTesting,
+};
+
+/// Holds the result of a successful Virtual Machine attestation request.
+/// See [`request_attestation`].
+#[derive(Debug)]
+pub struct AttestationResult {
+    result: NonNull<AVmAttestationResult>,
+}
+
+/// Error type that can be returned from an unsuccessful Virtual Machine attestation request.
+/// See [`request_attestation`].
+#[derive(Copy, Clone, Debug, Hash, PartialEq, Eq)]
+pub enum AttestationError {
+    /// The challenge size was not between 0 and 64 bytes (inclusive).
+    InvalidChallenge,
+    /// The attempt to attest the VM failed. A subsequent request may succeed.
+    AttestationFailed,
+    /// VM attestation is not supported in the current environment.
+    AttestationUnsupported,
+}
+
+impl Error for AttestationError {}
+
+impl Display for AttestationError {
+    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
+        let status = match self {
+            Self::InvalidChallenge => AVmAttestationStatus::ATTESTATION_ERROR_INVALID_CHALLENGE,
+            Self::AttestationFailed => AVmAttestationStatus::ATTESTATION_ERROR_ATTESTATION_FAILED,
+            Self::AttestationUnsupported => AVmAttestationStatus::ATTESTATION_ERROR_UNSUPPORTED,
+        };
+        // SAFETY: AVmAttestationStatus_toString always returns a non-null pointer to a
+        // nul-terminated C string with static lifetime (which is valid UTF-8).
+        let c_str = unsafe { CStr::from_ptr(AVmAttestationStatus_toString(status)) };
+        let str = c_str.to_str().expect("Invalid UTF-8 for AVmAttestationStatus");
+        f.write_str(str)
+    }
+}
+
+impl Drop for AttestationResult {
+    fn drop(&mut self) {
+        let ptr = self.result.as_ptr();
+
+        // SAFETY: The `result` field is private, and only populated with a successful call to
+        // `AVmPayload_requestAttestation`, and not freed elsewhere.
+        unsafe { AVmAttestationResult_free(ptr) };
+    }
+}
+
+// SAFETY: The API functions that accept the `AVmAttestationResult` pointer are all safe to call
+// from any thread, including `AVmAttestationResult_free` which is called only on drop.
+unsafe impl Send for AttestationResult {}
+
+// SAFETY: There is no interior mutation here; any future functions that might mutate the data would
+// require a non-const pointer and hence need `&mut self` here. The only existing such function is
+// `AVmAttestationResult_free` where we take a mutable reference guaranteeing no other references
+// exist. The raw API functions are safe to call from any thread.
+unsafe impl Sync for AttestationResult {}
+
+/// Requests the remote attestation of this VM.
+///
+/// On success the supplied [`challenge`] will be included in the certificate chain accessible from
+/// the [`AttestationResult`]; this can be used as proof of the freshness of the attestation.
+///
+/// The challenge should be no more than 64 bytes long or the request will fail.
+pub fn request_attestation(challenge: &[u8]) -> Result<AttestationResult, AttestationError> {
+    let mut result: *mut AVmAttestationResult = ptr::null_mut();
+    // SAFETY: We only read the challenge within its bounds and the function does not retain any
+    // reference to it.
+    let status = unsafe {
+        AVmPayload_requestAttestation(
+            challenge.as_ptr() as *const c_void,
+            challenge.len(),
+            &mut result,
+        )
+    };
+    AttestationResult::new(status, result)
+}
+
+/// A variant of [`request_attestation`] used for testing purposes. This should not be used by
+/// normal VMs, and is not available to app owned VMs.
+pub fn request_attestation_for_testing(
+    challenge: &[u8],
+) -> Result<AttestationResult, AttestationError> {
+    let mut result: *mut AVmAttestationResult = ptr::null_mut();
+    // SAFETY: We only read the challenge within its bounds and the function does not retain any
+    // reference to it.
+    let status = unsafe {
+        AVmPayload_requestAttestationForTesting(
+            challenge.as_ptr() as *const c_void,
+            challenge.len(),
+            &mut result,
+        )
+    };
+    AttestationResult::new(status, result)
+}
+
+impl AttestationResult {
+    fn new(
+        status: AVmAttestationStatus,
+        result: *mut AVmAttestationResult,
+    ) -> Result<AttestationResult, AttestationError> {
+        match status {
+            AVmAttestationStatus::ATTESTATION_ERROR_INVALID_CHALLENGE => {
+                Err(AttestationError::InvalidChallenge)
+            }
+            AVmAttestationStatus::ATTESTATION_ERROR_ATTESTATION_FAILED => {
+                Err(AttestationError::AttestationFailed)
+            }
+            AVmAttestationStatus::ATTESTATION_ERROR_UNSUPPORTED => {
+                Err(AttestationError::AttestationUnsupported)
+            }
+            AVmAttestationStatus::ATTESTATION_OK => {
+                let result = NonNull::new(result)
+                    .expect("Attestation succeeded but the attestation result is null");
+                Ok(AttestationResult { result })
+            }
+        }
+    }
+
+    fn as_const_ptr(&self) -> *const AVmAttestationResult {
+        self.result.as_ptr().cast_const()
+    }
+
+    /// Returns the attested private key. This is the ECDSA P-256 private key corresponding to the
+    /// public key described by the leaf certificate in the attested
+    /// [certificate chain](AttestationResult::certificate_chain). It is a DER-encoded
+    /// `ECPrivateKey` structure as specified in
+    /// [RFC 5915 s3](https://datatracker.ietf.org/doc/html/rfc5915#section-3).
+    ///
+    /// Note: The [`sign_message`](AttestationResult::sign_message) method allows signing with the
+    /// key without retrieving it.
+    pub fn private_key(&self) -> Vec<u8> {
+        let ptr = self.as_const_ptr();
+
+        let size =
+            // SAFETY: We own the `AVmAttestationResult` pointer, so it is valid. The function
+            // writes no data since we pass a zero size, and null is explicitly allowed for the
+            // destination in that case.
+            unsafe { AVmAttestationResult_getPrivateKey(ptr, ptr::null_mut(), 0) };
+
+        let mut private_key = vec![0u8; size];
+        // SAFETY: We own the `AVmAttestationResult` pointer, so it is valid. The function only
+        // writes within the bounds of `private_key`, which we just allocated so cannot be aliased.
+        let size = unsafe {
+            AVmAttestationResult_getPrivateKey(
+                ptr,
+                private_key.as_mut_ptr() as *mut c_void,
+                private_key.len(),
+            )
+        };
+        assert_eq!(size, private_key.len());
+        private_key
+    }
+
+    /// Signs the given message using the attested private key. The signature uses ECDSA P-256; the
+    /// message is first hashed with SHA-256 and then it is signed with the attested EC P-256
+    /// [private key](AttestationResult::private_key).
+    ///
+    /// The signature is a DER-encoded `ECDSASignature`` structure as described in
+    /// [RFC 6979](https://datatracker.ietf.org/doc/html/rfc6979).
+    pub fn sign_message(&self, message: &[u8]) -> Vec<u8> {
+        let ptr = self.as_const_ptr();
+
+        // SAFETY: We own the `AVmAttestationResult` pointer, so it is valid. The function
+        // writes no data since we pass a zero size, and null is explicitly allowed for the
+        // destination in that case.
+        let size = unsafe {
+            AVmAttestationResult_sign(
+                ptr,
+                message.as_ptr() as *const c_void,
+                message.len(),
+                ptr::null_mut(),
+                0,
+            )
+        };
+
+        let mut signature = vec![0u8; size];
+        // SAFETY: We own the `AVmAttestationResult` pointer, so it is valid. The function only
+        // writes within the bounds of `signature`, which we just allocated so cannot be aliased.
+        let size = unsafe {
+            AVmAttestationResult_sign(
+                ptr,
+                message.as_ptr() as *const c_void,
+                message.len(),
+                signature.as_mut_ptr() as *mut c_void,
+                signature.len(),
+            )
+        };
+        assert!(size <= signature.len());
+        signature.truncate(size);
+        signature
+    }
+
+    /// Returns an iterator over the certificates forming the certificate chain for the VM, and its
+    /// public key, obtained by the attestation process.
+    ///
+    /// The certificate chain consists of a sequence of DER-encoded X.509 certificates that form
+    /// the attestation key's certificate chain. It starts with the leaf certificate covering the
+    /// attested public key and ends with the root certificate.
+    pub fn certificate_chain(&self) -> CertIterator {
+        // SAFETY: We own the `AVmAttestationResult` pointer, so it is valid.
+        let count = unsafe { AVmAttestationResult_getCertificateCount(self.as_const_ptr()) };
+
+        CertIterator { result: self, count, current: 0 }
+    }
+
+    fn certificate(&self, index: usize) -> Vec<u8> {
+        let ptr = self.as_const_ptr();
+
+        let size =
+            // SAFETY: We own the `AVmAttestationResult` pointer, so it is valid. The function
+            // writes no data since we pass a zero size, and null is explicitly allowed for the
+            // destination in that case. The function will panic if `index` is out of range (which
+            // is safe).
+            unsafe { AVmAttestationResult_getCertificateAt(ptr, index, ptr::null_mut(), 0) };
+
+        let mut cert = vec![0u8; size];
+        // SAFETY: We own the `AVmAttestationResult` pointer, so it is valid. The function only
+        // writes within the bounds of `cert`, which we just allocated so cannot be aliased.
+        let size = unsafe {
+            AVmAttestationResult_getCertificateAt(
+                ptr,
+                index,
+                cert.as_mut_ptr() as *mut c_void,
+                cert.len(),
+            )
+        };
+        assert_eq!(size, cert.len());
+        cert
+    }
+}
+
+/// An iterator over the DER-encoded X.509 certificates containin in an [`AttestationResult`].
+/// See [`certificate_chain`](AttestationResult::certificate_chain) for more details.
+pub struct CertIterator<'a> {
+    result: &'a AttestationResult,
+    count: usize,
+    current: usize, // Invariant: current <= count
+}
+
+impl<'a> Iterator for CertIterator<'a> {
+    type Item = Vec<u8>;
+
+    fn next(&mut self) -> Option<Self::Item> {
+        if self.current < self.count {
+            let cert = self.result.certificate(self.current);
+            self.current += 1;
+            Some(cert)
+        } else {
+            None
+        }
+    }
+
+    fn size_hint(&self) -> (usize, Option<usize>) {
+        let size = self.count - self.current;
+        (size, Some(size))
+    }
+}
+
+impl<'a> ExactSizeIterator for CertIterator<'a> {}
+impl<'a> FusedIterator for CertIterator<'a> {}
diff --git a/vm_payload/wrapper/lib.rs b/vm_payload/wrapper/lib.rs
new file mode 100644
index 0000000..bc26802
--- /dev/null
+++ b/vm_payload/wrapper/lib.rs
@@ -0,0 +1,188 @@
+/*
+ * 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.
+ */
+
+//! Rust wrapper for the VM Payload API, allowing virtual machine payload code to be written in
+//! Rust. This wraps the raw C API, accessed via bindgen, into a more idiomatic Rust interface.
+//!
+//! See `https://cs.android.com/android/platform/superproject/main/+/main:packages/modules/Virtualization/vm_payload/README.md`
+//! for more information on the VM Payload API.
+
+mod attestation;
+
+pub use attestation::{
+    request_attestation, request_attestation_for_testing, AttestationError, AttestationResult,
+};
+use binder::unstable_api::AsNative;
+use binder::{FromIBinder, Strong};
+use std::ffi::{c_void, CStr, OsStr};
+use std::os::unix::ffi::OsStrExt;
+use std::path::Path;
+use std::ptr;
+use vm_payload_bindgen::{
+    AIBinder, AVmPayload_getApkContentsPath, AVmPayload_getEncryptedStoragePath,
+    AVmPayload_getVmInstanceSecret, AVmPayload_notifyPayloadReady, AVmPayload_runVsockRpcServer,
+};
+
+/// Marks the main function of the VM payload.
+///
+/// When the VM is run, this function is called. If it returns, the VM ends normally with a 0 exit
+/// code.
+///
+/// Example:
+///
+/// ```rust
+/// use log::info;
+///
+/// vm_payload::main!(vm_main);
+///
+/// fn vm_main() {
+///     android_logger::init_once(
+///          android_logger::Config::default()
+///             .with_tag("example_vm_payload")
+///             .with_max_level(log::LevelFilter::Info),
+///     );
+///     info!("Hello world");
+/// }
+/// ```
+#[macro_export]
+macro_rules! main {
+    ($name:path) => {
+        // Export a symbol with a name matching the extern declaration below.
+        #[export_name = "rust_main"]
+        fn __main() {
+            // Ensure that the main function provided by the application has the correct type.
+            $name()
+        }
+    };
+}
+
+// This is the real C entry point for the VM; we just forward to the Rust entry point.
+#[allow(non_snake_case)]
+#[no_mangle]
+extern "C" fn AVmPayload_main() {
+    extern "Rust" {
+        fn rust_main();
+    }
+
+    // SAFETY: rust_main is provided by the application using the `main!` macro above, which makes
+    // sure it has the right type.
+    unsafe { rust_main() }
+}
+
+/// Notifies the host that the payload is ready.
+///
+/// If the host app has set a `VirtualMachineCallback` for the VM, its
+/// `onPayloadReady` method will be called.
+///
+/// Note that subsequent calls to this function after the first have no effect;
+/// `onPayloadReady` is never called more than once.
+pub fn notify_payload_ready() {
+    // SAFETY: Invokes a method from the bindgen library `vm_payload_bindgen` which is safe to
+    // call at any time.
+    unsafe { AVmPayload_notifyPayloadReady() };
+}
+
+/// Runs a binder RPC server, serving the supplied binder service implementation on the given vsock
+/// port.
+///
+/// If and when the server is ready for connections (i.e. it is listening on the port),
+/// [`notify_payload_ready`] is called to notify the host that the server is ready. This is
+/// appropriate for VM payloads that serve a single binder service - which is common.
+///
+/// Note that this function does not return. The calling thread joins the binder
+/// thread pool to handle incoming messages.
+pub fn run_single_vsock_service<T>(service: Strong<T>, port: u32) -> !
+where
+    T: FromIBinder + ?Sized,
+{
+    extern "C" fn on_ready(_param: *mut c_void) {
+        notify_payload_ready();
+    }
+
+    let mut service = service.as_binder();
+    // The cast here is needed because the compiler doesn't know that our vm_payload_bindgen
+    // AIBinder is the same type as binder_ndk_sys::AIBinder.
+    let service = service.as_native_mut() as *mut AIBinder;
+    let param = ptr::null_mut();
+    // SAFETY: We have a strong reference to the service, so the raw pointer remains valid. It is
+    // safe for on_ready to be invoked at any time, with any parameter.
+    unsafe { AVmPayload_runVsockRpcServer(service, port, Some(on_ready), param) }
+}
+
+/// Gets the path to the contents of the APK containing the VM payload. It is a directory, under
+/// which are the unzipped contents of the APK containing the payload, all read-only
+/// but accessible to the payload.
+pub fn apk_contents_path() -> &'static Path {
+    // SAFETY: AVmPayload_getApkContentsPath always returns a non-null pointer to a
+    // nul-terminated C string with static lifetime.
+    let c_str = unsafe { CStr::from_ptr(AVmPayload_getApkContentsPath()) };
+    Path::new(OsStr::from_bytes(c_str.to_bytes()))
+}
+
+/// Gets the path to the encrypted persistent storage for the VM, if any. This is
+/// a directory under which any files or directories created will be stored on
+/// behalf of the VM by the host app. All data is encrypted using a key known
+/// only to the VM, so the host cannot decrypt it, but may delete it.
+///
+/// Returns `None` if no encrypted storage was requested in the VM configuration.
+pub fn encrypted_storage_path() -> Option<&'static Path> {
+    // SAFETY: AVmPayload_getEncryptedStoragePath returns either null or a pointer to a
+    // nul-terminated C string with static lifetime.
+    let ptr = unsafe { AVmPayload_getEncryptedStoragePath() };
+    if ptr.is_null() {
+        None
+    } else {
+        // SAFETY: We know the pointer is not null, and so it is a valid C string.
+        let c_str = unsafe { CStr::from_ptr(ptr) };
+        Some(Path::new(OsStr::from_bytes(c_str.to_bytes())))
+    }
+}
+
+/// Retrieves all or part of a 32-byte secret that is bound to this unique VM
+/// instance and the supplied identifier. The secret can be used e.g. as an
+/// encryption key.
+///
+/// Every VM has a secret that is derived from a device-specific value known to
+/// the hypervisor, the code that runs in the VM and its non-modifiable
+/// configuration; it is not made available to the host OS.
+///
+/// This function performs a further derivation from the VM secret and the
+/// supplied identifier. As long as the VM identity doesn't change the same value
+/// will be returned for the same identifier, even if the VM is stopped &
+/// restarted or the device rebooted.
+///
+/// If multiple secrets are required for different purposes, a different
+/// identifier should be used for each. The identifiers otherwise are arbitrary
+/// byte sequences and do not need to be kept secret; typically they are
+/// hardcoded in the calling code.
+///
+/// The secret is returned in [`secret`], truncated to its size, which must be between
+/// 1 and 32 bytes (inclusive) or the function will panic.
+pub fn get_vm_instance_secret<const N: usize>(identifier: &[u8], secret: &mut [u8]) {
+    let secret_size = secret.len();
+    assert!((1..=32).contains(&secret_size), "VM instance secrets can be up to 32 bytes long");
+
+    // SAFETY: The function only reads from `[identifier]` within its bounds, and only writes to
+    // `[secret]` within its bounds. Neither reference is retained, and we know neither is null.
+    unsafe {
+        AVmPayload_getVmInstanceSecret(
+            identifier.as_ptr() as *const c_void,
+            identifier.len(),
+            secret.as_mut_ptr() as *mut c_void,
+            secret_size,
+        )
+    }
+}