Merge "Fix typo in comment" into main
diff --git a/TEST_MAPPING b/TEST_MAPPING
index 3bc7aba..171389b 100644
--- a/TEST_MAPPING
+++ b/TEST_MAPPING
@@ -28,6 +28,9 @@
     },
     {
       "name": "initrd_bootconfig.test"
+    },
+    {
+      "name": "libdice_policy.test"
     }
   ],
   "avf-postsubmit": [
diff --git a/libs/dice_policy/Android.bp b/libs/dice_policy/Android.bp
new file mode 100644
index 0000000..a7ac5b9
--- /dev/null
+++ b/libs/dice_policy/Android.bp
@@ -0,0 +1,35 @@
+package {
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+rust_defaults {
+    name: "libdice_policy.defaults",
+    crate_name: "dice_policy",
+    defaults: ["avf_build_flags_rust"],
+    srcs: ["src/lib.rs"],
+    edition: "2021",
+    prefer_rlib: true,
+    rustlibs: [
+        "libanyhow",
+        "libciborium",
+        "libcoset",
+    ],
+}
+
+rust_library {
+    name: "libdice_policy",
+    defaults: ["libdice_policy.defaults"],
+}
+
+rust_test {
+    name: "libdice_policy.test",
+    defaults: [
+        "libdice_policy.defaults",
+        "rdroidtest.defaults",
+    ],
+    test_suites: ["general-tests"],
+    rustlibs: [
+        "librustutils",
+        "libscopeguard",
+    ],
+}
diff --git a/libs/dice_policy/src/lib.rs b/libs/dice_policy/src/lib.rs
new file mode 100644
index 0000000..f5d117c
--- /dev/null
+++ b/libs/dice_policy/src/lib.rs
@@ -0,0 +1,346 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+//! A “DICE policy” is a format for setting constraints on a DICE chain. A DICE chain policy
+//! verifier takes a policy and a DICE chain, and returns a boolean indicating whether the
+//! DICE chain meets the constraints set out on a policy.
+//!
+//! This forms the foundation of Dice Policy aware Authentication (DPA-Auth), where the server
+//! authenticates a client by comparing its dice chain against a set policy.
+//!
+//! Another use is "sealing", where clients can use an appropriately constructed dice policy to
+//! seal a secret. Unsealing is only permitted if dice chain of the component requesting unsealing
+//! complies with the policy.
+//!
+//! A typical policy will assert things like:
+//! # DK_pub must have this value
+//! # The DICE chain must be exactly five certificates long
+//! # authorityHash in the third certificate must have this value
+//! securityVersion in the fourth certificate must be an integer greater than 8
+//!
+//! These constraints used to express policy are (for now) limited to following 2 types:
+//! 1. Exact Match: useful for enforcing rules like authority hash should be exactly equal.
+//! 2. Greater than or equal to: Useful for setting policies that seal
+//! Anti-rollback protected entities (should be accessible to versions >= present).
+//!
+//! Dice Policy CDDL:
+//!
+//! dicePolicy = [
+//! 1, ; dice policy version
+//! + nodeConstraintList ; for each entry in dice chain
+//! ]
+//!
+//! nodeConstraintList = [
+//!     * nodeConstraint
+//! ]
+//!
+//! ; We may add a hashConstraint item later
+//! nodeConstraint = exactMatchConstraint / geConstraint
+//!
+//! exactMatchConstraint = [1, keySpec, value]
+//! geConstraint = [2, keySpec, int]
+//!
+//! keySpec = [value+]
+//!
+//! value = bool / int / tstr / bstr
+
+use anyhow::{anyhow, bail, Context, Result};
+use ciborium::Value;
+use coset::{AsCborValue, CoseSign1};
+use std::borrow::Cow;
+
+const DICE_POLICY_VERSION: u64 = 1;
+
+/// Constraint Types supported in Dice policy.
+#[non_exhaustive]
+#[derive(Clone, Copy, Debug, PartialEq)]
+pub enum ConstraintType {
+    /// Enforce exact match criteria, indicating the policy should match
+    /// if the dice chain has exact same specified values.
+    ExactMatch = 1,
+    /// Enforce Greater than or equal to criteria. When applied on security_version, this
+    /// can be useful to set policy that matches dice chains with same or upgraded images.
+    GreaterOrEqual = 2,
+}
+
+/// ConstraintSpec is used to specify which constraint type to apply and
+/// on which all entries in a dice node.
+/// See documentation of `from_dice_chain()` for examples.
+pub struct ConstraintSpec {
+    constraint_type: ConstraintType,
+    // path is essentially a list of label/int.
+    // It identifies which entry (in a dice node) to be applying constraints on.
+    path: Vec<i64>,
+}
+
+impl ConstraintSpec {
+    /// Construct the ConstraintSpec.
+    pub fn new(constraint_type: ConstraintType, path: Vec<i64>) -> Result<Self> {
+        Ok(ConstraintSpec { constraint_type, path })
+    }
+}
+
+// TODO(b/291238565): Restrict (nested_)key & value type to (bool/int/tstr/bstr).
+// and maybe convert it into struct.
+/// Each constraint (on a dice node) is a tuple: (ConstraintType, constraint_path, value)
+#[derive(Debug, PartialEq)]
+struct Constraint(u16, Vec<i64>, Value);
+
+/// List of all constraints on a dice node.
+#[derive(Debug, PartialEq)]
+struct NodeConstraints(Box<[Constraint]>);
+
+/// Module for working with dice policy.
+#[derive(Debug, PartialEq)]
+pub struct DicePolicy {
+    version: u64,
+    node_constraints_list: Box<[NodeConstraints]>, // Constraint on each entry in dice chain.
+}
+
+impl DicePolicy {
+    /// Construct a dice policy from a given dice chain.
+    /// This can be used by clients to construct a policy to seal secrets.
+    /// Constraints on all but first dice node is applied using constraint_spec argument.
+    /// For the first node (which is a ROT key), the constraint is ExactMatch of the whole node.
+    ///
+    /// # Arguments
+    /// `dice_chain`: The serialized CBOR encoded Dice chain, adhering to Android Profile for DICE.
+    /// https://pigweed.googlesource.com/open-dice/+/refs/heads/main/docs/android.md
+    ///
+    /// `constraint_spec`: List of constraints to be applied on dice node.
+    /// Each constraint is a ConstraintSpec object.
+    ///
+    /// Note: Dice node is treated as a nested map (& so the lookup is done in that fashion).
+    ///
+    /// Examples of constraint_spec:
+    ///  1. For exact_match on auth_hash & greater_or_equal on security_version
+    ///    constraint_spec =[
+    ///     (ConstraintType::ExactMatch, vec![AUTHORITY_HASH]),
+    ///     (ConstraintType::GreaterOrEqual, vec![CONFIG_DESC, COMPONENT_NAME]),
+    ///    ];
+    ///
+    /// 2. For hypothetical (and highly simplified) dice chain:
+    ///    [ROT_KEY, [{1 : 'a', 2 : {200 : 5, 201 : 'b'}}]]
+    ///    The following can be used
+    ///    constraint_spec =[
+    ///     ConstraintSpec(ConstraintType::ExactMatch, vec![1]),         // exact_matches value 'a'
+    ///     ConstraintSpec(ConstraintType::GreaterOrEqual, vec![2, 200]),// matches any value >= 5
+    ///    ];
+    pub fn from_dice_chain(dice_chain: &[u8], constraint_spec: &[ConstraintSpec]) -> Result<Self> {
+        // TODO(b/298217847): Check if the given dice chain adheres to Explicit-key DiceCertChain
+        // format and if not, convert it before policy construction.
+        let dice_chain = value_from_bytes(dice_chain).context("Unable to decode top-level CBOR")?;
+        let dice_chain = match dice_chain {
+            Value::Array(array) if array.len() >= 2 => array,
+            _ => bail!("Expected an array of at least length 2, found: {:?}", dice_chain),
+        };
+        let mut constraints_list: Vec<NodeConstraints> = Vec::with_capacity(dice_chain.len());
+        let mut it = dice_chain.into_iter();
+
+        constraints_list.push(NodeConstraints(Box::new([Constraint(
+            ConstraintType::ExactMatch as u16,
+            Vec::new(),
+            it.next().unwrap(),
+        )])));
+
+        for (n, value) in it.enumerate() {
+            let entry = cbor_value_from_cose_sign(value)
+                .with_context(|| format!("Unable to get Cose payload at: {}", n))?;
+            constraints_list.push(payload_to_constraints(entry, constraint_spec)?);
+        }
+
+        Ok(DicePolicy {
+            version: DICE_POLICY_VERSION,
+            node_constraints_list: constraints_list.into_boxed_slice(),
+        })
+    }
+}
+
+// Take the payload of a dice node & construct the constraints on it.
+fn payload_to_constraints(
+    payload: Value,
+    constraint_spec: &[ConstraintSpec],
+) -> Result<NodeConstraints> {
+    let mut node_constraints: Vec<Constraint> = Vec::new();
+    for constraint_item in constraint_spec {
+        let constraint_path = constraint_item.path.to_vec();
+        if constraint_path.is_empty() {
+            bail!("Expected non-empty key spec");
+        }
+        let val = lookup_value_in_nested_map(&payload, &constraint_path)
+            .context(format!("Value not found for constraint_path {:?}", constraint_path))?;
+        let constraint = Constraint(constraint_item.constraint_type as u16, constraint_path, val);
+        node_constraints.push(constraint);
+    }
+    Ok(NodeConstraints(node_constraints.into_boxed_slice()))
+}
+
+// Lookup value corresponding to constraint path in nested map.
+// This function recursively calls itself.
+// The depth of recursion is limited by the size of constraint_path.
+fn lookup_value_in_nested_map(cbor_map: &Value, constraint_path: &[i64]) -> Result<Value> {
+    if constraint_path.is_empty() {
+        return Ok(cbor_map.clone());
+    }
+    let explicit_map = get_map_from_value(cbor_map)?;
+    let val = lookup_value_in_map(&explicit_map, constraint_path[0])
+        .ok_or(anyhow!("Value not found for constraint key: {:?}", constraint_path[0]))?;
+    lookup_value_in_nested_map(val, &constraint_path[1..])
+}
+
+fn get_map_from_value(cbor_map: &Value) -> Result<Cow<Vec<(Value, Value)>>> {
+    match cbor_map {
+        Value::Bytes(b) => value_from_bytes(b)?
+            .into_map()
+            .map(Cow::Owned)
+            .map_err(|e| anyhow!("Expected a cbor map: {:?}", e)),
+        Value::Map(map) => Ok(Cow::Borrowed(map)),
+        _ => bail!("/Expected a cbor map {:?}", cbor_map),
+    }
+}
+
+fn lookup_value_in_map(map: &[(Value, Value)], key: i64) -> Option<&Value> {
+    let key = Value::Integer(key.into());
+    for (k, v) in map.iter() {
+        if k == &key {
+            return Some(v);
+        }
+    }
+    None
+}
+
+/// Extract the payload from the COSE Sign
+fn cbor_value_from_cose_sign(cbor: Value) -> Result<Value> {
+    let sign1 =
+        CoseSign1::from_cbor_value(cbor).map_err(|e| anyhow!("Error extracting CoseKey: {}", e))?;
+    match sign1.payload {
+        None => bail!("Missing payload"),
+        Some(payload) => Ok(value_from_bytes(&payload)?),
+    }
+}
+
+/// Decodes the provided binary CBOR-encoded value and returns a
+/// ciborium::Value struct wrapped in Result.
+fn value_from_bytes(mut bytes: &[u8]) -> Result<Value> {
+    let value = ciborium::de::from_reader(&mut bytes)?;
+    // Ciborium tries to read one Value, & doesn't care if there is trailing data after it. We do.
+    if !bytes.is_empty() {
+        bail!("Unexpected trailing data while converting to CBOR value");
+    }
+    Ok(value)
+}
+
+#[cfg(test)]
+rdroidtest::test_main!();
+
+#[cfg(test)]
+mod tests {
+    use super::*;
+    use ciborium::cbor;
+    use coset::{CoseKey, Header, ProtectedHeader};
+    use rdroidtest::test;
+
+    const AUTHORITY_HASH: i64 = -4670549;
+    const CONFIG_DESC: i64 = -4670548;
+    const COMPONENT_NAME: i64 = -70002;
+    const KEY_MODE: i64 = -4670551;
+
+    // This is the number of certs in compos bcc (including the first ROT)
+    // To analyze a bcc use hwtrust tool from /tools/security/remote_provisioning/hwtrust
+    // `hwtrust --verbose dice-chain [path]/composbcc`
+    const COMPOS_DICE_CHAIN_SIZE: usize = 5;
+    const EXAMPLE_STRING: &str = "testing_dice_policy";
+    const EXAMPLE_NUM: i64 = 59765;
+
+    test!(policy_dice_size_is_same);
+    fn policy_dice_size_is_same() {
+        let input_dice = include_bytes!("../testdata/composbcc");
+        let constraint_spec = [
+            ConstraintSpec::new(ConstraintType::ExactMatch, vec![AUTHORITY_HASH]).unwrap(),
+            ConstraintSpec::new(ConstraintType::ExactMatch, vec![KEY_MODE]).unwrap(),
+            ConstraintSpec::new(ConstraintType::GreaterOrEqual, vec![CONFIG_DESC, COMPONENT_NAME])
+                .unwrap(),
+        ];
+        let policy = DicePolicy::from_dice_chain(input_dice, &constraint_spec).unwrap();
+        assert_eq!(policy.node_constraints_list.len(), COMPOS_DICE_CHAIN_SIZE);
+    }
+
+    test!(policy_structure_check);
+    fn policy_structure_check() {
+        let rot_key = CoseKey::default().to_cbor_value().unwrap();
+        let nested_payload = cbor!({
+            100 => EXAMPLE_NUM
+        })
+        .unwrap();
+        let payload = cbor!({
+            1 => EXAMPLE_STRING,
+            2 => "some_other_example_string",
+            3 => Value::Bytes(value_to_bytes(&nested_payload).unwrap()),
+        })
+        .unwrap();
+        let payload = value_to_bytes(&payload).unwrap();
+        let dice_node = CoseSign1 {
+            protected: ProtectedHeader::default(),
+            unprotected: Header::default(),
+            payload: Some(payload),
+            signature: b"ddef".to_vec(),
+        }
+        .to_cbor_value()
+        .unwrap();
+        let input_dice = Value::Array([rot_key.clone(), dice_node].to_vec());
+
+        let input_dice = value_to_bytes(&input_dice).unwrap();
+        let constraint_spec = [
+            ConstraintSpec::new(ConstraintType::ExactMatch, vec![1]).unwrap(),
+            ConstraintSpec::new(ConstraintType::GreaterOrEqual, vec![3, 100]).unwrap(),
+        ];
+        let policy = DicePolicy::from_dice_chain(&input_dice, &constraint_spec).unwrap();
+
+        // Assert policy is exactly as expected!
+        assert_eq!(
+            policy,
+            DicePolicy {
+                version: 1,
+                node_constraints_list: Box::new([
+                    NodeConstraints(Box::new([Constraint(
+                        ConstraintType::ExactMatch as u16,
+                        vec![],
+                        rot_key
+                    )])),
+                    NodeConstraints(Box::new([
+                        Constraint(
+                            ConstraintType::ExactMatch as u16,
+                            vec![1],
+                            Value::Text(EXAMPLE_STRING.to_string())
+                        ),
+                        Constraint(
+                            ConstraintType::GreaterOrEqual as u16,
+                            vec![3, 100],
+                            Value::from(EXAMPLE_NUM)
+                        )
+                    ])),
+                ])
+            }
+        );
+    }
+
+    /// Encodes a ciborium::Value into bytes.
+    fn value_to_bytes(value: &Value) -> Result<Vec<u8>> {
+        let mut bytes: Vec<u8> = Vec::new();
+        ciborium::ser::into_writer(&value, &mut bytes)?;
+        Ok(bytes)
+    }
+}
diff --git a/libs/dice_policy/testdata/composbcc b/libs/dice_policy/testdata/composbcc
new file mode 100644
index 0000000..fb3e006
--- /dev/null
+++ b/libs/dice_policy/testdata/composbcc
Binary files differ
diff --git a/rialto/Android.bp b/rialto/Android.bp
index 3dfcca1..0b26ccc 100644
--- a/rialto/Android.bp
+++ b/rialto/Android.bp
@@ -103,13 +103,12 @@
         "android.system.virtualizationservice-rust",
         "libandroid_logger",
         "libanyhow",
-        "libciborium",
         "liblibc",
         "liblog_rust",
         "libnix",
         "libservice_vm_comm",
+        "libservice_vm_manager",
         "libvmclient",
-        "libvsock",
     ],
     data: [
         ":rialto_bin",
diff --git a/rialto/tests/test.rs b/rialto/tests/test.rs
index f7df217..07cfd54 100644
--- a/rialto/tests/test.rs
+++ b/rialto/tests/test.rs
@@ -17,76 +17,92 @@
 use android_system_virtualizationservice::{
     aidl::android::system::virtualizationservice::{
         CpuTopology::CpuTopology, DiskImage::DiskImage, Partition::Partition,
-        PartitionType::PartitionType, VirtualMachineConfig::VirtualMachineConfig,
+        VirtualMachineConfig::VirtualMachineConfig,
         VirtualMachineRawConfig::VirtualMachineRawConfig,
     },
     binder::{ParcelFileDescriptor, ProcessState},
 };
-use anyhow::{anyhow, bail, Context, Result};
+use anyhow::{Context, Result};
 use log::info;
 use service_vm_comm::{Request, Response, VmType};
+use service_vm_manager::ServiceVm;
 use std::fs::File;
-use std::io::{self, BufRead, BufReader, BufWriter, Write};
+use std::io::{self, BufRead, BufReader};
 use std::os::unix::io::FromRawFd;
 use std::panic;
+use std::path::PathBuf;
 use std::thread;
-use std::time::Duration;
-use vmclient::{DeathReason, VmInstance};
-use vsock::{VsockListener, VMADDR_CID_HOST};
+use vmclient::VmInstance;
 
 const SIGNED_RIALTO_PATH: &str = "/data/local/tmp/rialto_test/arm64/rialto.bin";
 const UNSIGNED_RIALTO_PATH: &str = "/data/local/tmp/rialto_test/arm64/rialto_unsigned.bin";
 const INSTANCE_IMG_PATH: &str = "/data/local/tmp/rialto_test/arm64/instance.img";
-const INSTANCE_IMG_SIZE: i64 = 1024 * 1024; // 1MB
 
-#[test]
-fn boot_rialto_in_protected_vm_successfully() -> Result<()> {
-    boot_rialto_successfully(SIGNED_RIALTO_PATH, VmType::ProtectedVm)
+fn rialto_path(vm_type: VmType) -> &'static str {
+    match vm_type {
+        VmType::ProtectedVm => SIGNED_RIALTO_PATH,
+        VmType::NonProtectedVm => UNSIGNED_RIALTO_PATH,
+    }
 }
 
 #[test]
-fn boot_rialto_in_unprotected_vm_successfully() -> Result<()> {
-    boot_rialto_successfully(UNSIGNED_RIALTO_PATH, VmType::NonProtectedVm)
+fn process_requests_in_protected_vm() -> Result<()> {
+    let mut vm = start_service_vm(VmType::ProtectedVm)?;
+
+    check_processing_reverse_request(&mut vm)?;
+    Ok(())
 }
 
-fn boot_rialto_successfully(rialto_path: &str, vm_type: VmType) -> Result<()> {
+#[test]
+fn process_requests_in_non_protected_vm() -> Result<()> {
+    let mut vm = start_service_vm(VmType::NonProtectedVm)?;
+
+    check_processing_reverse_request(&mut vm)?;
+    Ok(())
+}
+
+fn check_processing_reverse_request(vm: &mut ServiceVm) -> Result<()> {
+    // TODO(b/292080257): Test with message longer than the receiver's buffer capacity
+    // 1024 bytes once the guest virtio-vsock driver fixes the credit update in recv().
+    let message = "abc".repeat(166);
+    let request = Request::Reverse(message.as_bytes().to_vec());
+
+    let response = vm.process_request(&request)?;
+    info!("Received response '{response:?}' for the request '{request:?}'.");
+
+    let expected_response: Vec<u8> = message.as_bytes().iter().rev().cloned().collect();
+    assert_eq!(Response::Reverse(expected_response), response);
+    Ok(())
+}
+
+fn start_service_vm(vm_type: VmType) -> Result<ServiceVm> {
     android_logger::init_once(
         android_logger::Config::default().with_tag("rialto").with_min_level(log::Level::Debug),
     );
-
     // Redirect panic messages to logcat.
     panic::set_hook(Box::new(|panic_info| {
         log::error!("{}", panic_info);
     }));
-
     // We need to start the thread pool for Binder to work properly, especially link_to_death.
     ProcessState::start_thread_pool();
+    ServiceVm::start_vm(vm_instance(vm_type)?, vm_type)
+}
 
+fn vm_instance(vm_type: VmType) -> Result<VmInstance> {
     let virtmgr =
         vmclient::VirtualizationService::new().context("Failed to spawn VirtualizationService")?;
     let service = virtmgr.connect().context("Failed to connect to VirtualizationService")?;
 
-    let rialto = File::open(rialto_path).context("Failed to open Rialto kernel binary")?;
+    let rialto = File::open(rialto_path(vm_type)).context("Failed to open Rialto kernel binary")?;
     let console = android_log_fd()?;
     let log = android_log_fd()?;
 
     let disks = match vm_type {
         VmType::ProtectedVm => {
-            let instance_img = File::options()
-                .create(true)
-                .read(true)
-                .write(true)
-                .truncate(true)
-                .open(INSTANCE_IMG_PATH)?;
-            let instance_img = ParcelFileDescriptor::new(instance_img);
-
-            service
-                .initializeWritablePartition(
-                    &instance_img,
-                    INSTANCE_IMG_SIZE,
-                    PartitionType::ANDROID_VM_INSTANCE,
-                )
-                .context("Failed to initialize instange.img")?;
+            let instance_img = service_vm_manager::instance_img(
+                service.as_ref(),
+                PathBuf::from(INSTANCE_IMG_PATH),
+            )?;
             let writable_partitions = vec![Partition {
                 label: "vm-instance".to_owned(),
                 image: Some(instance_img),
@@ -96,7 +112,6 @@
         }
         VmType::NonProtectedVm => vec![],
     };
-
     let config = VirtualMachineConfig::RawConfig(VirtualMachineRawConfig {
         name: String::from("RialtoTest"),
         kernel: None,
@@ -111,7 +126,7 @@
         gdbPort: 0, // No gdb
         ..Default::default()
     });
-    let vm = VmInstance::create(
+    VmInstance::create(
         service.as_ref(),
         &config,
         Some(console),
@@ -119,29 +134,7 @@
         Some(log),
         None,
     )
-    .context("Failed to create VM")?;
-
-    let check_socket_handle =
-        thread::spawn(move || try_check_socket_connection(vm_type.port()).unwrap());
-
-    vm.start().context("Failed to start VM")?;
-
-    // Wait for VM to finish, and check that it shut down cleanly.
-    let death_reason = vm
-        .wait_for_death_with_timeout(Duration::from_secs(10))
-        .ok_or_else(|| anyhow!("Timed out waiting for VM exit"))?;
-    assert_eq!(death_reason, DeathReason::Shutdown);
-
-    match check_socket_handle.join() {
-        Ok(_) => {
-            info!(
-                "Received the echoed message. \
-                   The socket connection between the host and the service VM works correctly."
-            )
-        }
-        Err(_) => bail!("The socket connection check failed."),
-    }
-    Ok(())
+    .context("Failed to create VM")
 }
 
 fn android_log_fd() -> io::Result<File> {
@@ -159,32 +152,3 @@
     });
     Ok(writer)
 }
-
-fn try_check_socket_connection(port: u32) -> Result<()> {
-    info!("Setting up the listening socket on port {port}...");
-    let listener = VsockListener::bind_with_cid_port(VMADDR_CID_HOST, port)?;
-    info!("Listening on port {port}...");
-
-    let mut vsock_stream =
-        listener.incoming().next().ok_or_else(|| anyhow!("Failed to get vsock_stream"))??;
-    info!("Accepted connection {:?}", vsock_stream);
-    vsock_stream.set_read_timeout(Some(Duration::from_millis(1_000)))?;
-
-    const WRITE_BUFFER_CAPACITY: usize = 512;
-    let mut buffer = BufWriter::with_capacity(WRITE_BUFFER_CAPACITY, vsock_stream.clone());
-
-    // TODO(b/292080257): Test with message longer than the receiver's buffer capacity
-    // 1024 bytes once the guest virtio-vsock driver fixes the credit update in recv().
-    let message = "abc".repeat(166);
-    let request = Request::Reverse(message.as_bytes().to_vec());
-    ciborium::into_writer(&request, &mut buffer)?;
-    buffer.flush()?;
-    info!("Sent request: {request:?}.");
-
-    let response: Response = ciborium::from_reader(&mut vsock_stream)?;
-    info!("Received response: {response:?}.");
-
-    let expected_response: Vec<u8> = message.as_bytes().iter().rev().cloned().collect();
-    assert_eq!(Response::Reverse(expected_response), response);
-    Ok(())
-}
diff --git a/rustfmt.toml b/rustfmt.toml
deleted file mode 120000
index 475ba8f..0000000
--- a/rustfmt.toml
+++ /dev/null
@@ -1 +0,0 @@
-../../../build/soong/scripts/rustfmt.toml
\ No newline at end of file
diff --git a/rustfmt.toml b/rustfmt.toml
new file mode 100644
index 0000000..aaf15f2
--- /dev/null
+++ b/rustfmt.toml
@@ -0,0 +1,10 @@
+# Android Format Style
+# Should be in sync with build/soong/scripts/rustfmt.toml
+
+edition = "2021"
+use_small_heuristics = "Max"
+newline_style = "Unix"
+
+# Local customizations
+wrap_comments = true
+comment_width = 100
diff --git a/service_vm/client_apk/src/main.rs b/service_vm/client_apk/src/main.rs
index 672dd4a..08d4168 100644
--- a/service_vm/client_apk/src/main.rs
+++ b/service_vm/client_apk/src/main.rs
@@ -41,6 +41,7 @@
 fn try_main() -> Result<()> {
     info!("Welcome to Service VM Client!");
     let csr = b"Hello from Service VM";
+    info!("Sending: {:?}", csr);
     let certificate = request_certificate(csr);
     info!("Certificate: {:?}", certificate);
     Ok(())
diff --git a/service_vm_manager/Android.bp b/service_vm_manager/Android.bp
new file mode 100644
index 0000000..47867f5
--- /dev/null
+++ b/service_vm_manager/Android.bp
@@ -0,0 +1,23 @@
+package {
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+rust_library {
+    name: "libservice_vm_manager",
+    crate_name: "service_vm_manager",
+    defaults: ["avf_build_flags_rust"],
+    srcs: ["src/lib.rs"],
+    prefer_rlib: true,
+    rustlibs: [
+        "android.system.virtualizationservice-rust",
+        "libanyhow",
+        "libciborium",
+        "liblog_rust",
+        "libservice_vm_comm",
+        "libvmclient",
+        "libvsock",
+    ],
+    apex_available: [
+        "com.android.virt",
+    ],
+}
diff --git a/service_vm_manager/src/lib.rs b/service_vm_manager/src/lib.rs
new file mode 100644
index 0000000..484b57b
--- /dev/null
+++ b/service_vm_manager/src/lib.rs
@@ -0,0 +1,181 @@
+// Copyright 2023, The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT 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 module contains the functions to start, stop and communicate with the
+//! Service VM.
+
+use android_system_virtualizationservice::{
+    aidl::android::system::virtualizationservice::{
+        CpuTopology::CpuTopology, DiskImage::DiskImage,
+        IVirtualizationService::IVirtualizationService, Partition::Partition,
+        PartitionType::PartitionType, VirtualMachineConfig::VirtualMachineConfig,
+        VirtualMachineRawConfig::VirtualMachineRawConfig,
+    },
+    binder::ParcelFileDescriptor,
+};
+use anyhow::{ensure, Context, Result};
+use log::{info, warn};
+use service_vm_comm::{Request, Response, VmType};
+use std::fs::{File, OpenOptions};
+use std::io::{BufWriter, Write};
+use std::path::{Path, PathBuf};
+use std::time::Duration;
+use vmclient::VmInstance;
+use vsock::{VsockListener, VsockStream, VMADDR_CID_HOST};
+
+const VIRT_DATA_DIR: &str = "/data/misc/apexdata/com.android.virt";
+const RIALTO_PATH: &str = "/apex/com.android.virt/etc/rialto.bin";
+const INSTANCE_IMG_NAME: &str = "service_vm_instance.img";
+const INSTANCE_IMG_SIZE_BYTES: i64 = 1 << 20; // 1MB
+const MEMORY_MB: i32 = 300;
+const WRITE_BUFFER_CAPACITY: usize = 512;
+const READ_TIMEOUT: Duration = Duration::from_secs(10);
+const WRITE_TIMEOUT: Duration = Duration::from_secs(10);
+
+/// Service VM.
+pub struct ServiceVm {
+    vsock_stream: VsockStream,
+    /// VmInstance will be dropped when ServiceVm goes out of scope, which will kill the VM.
+    vm: VmInstance,
+}
+
+impl ServiceVm {
+    /// Starts the service VM and returns its instance.
+    /// The same instance image is used for different VMs.
+    /// TODO(b/278858244): Allow only one service VM running at each time.
+    pub fn start() -> Result<Self> {
+        let vm = vm_instance()?;
+        Self::start_vm(vm, VmType::ProtectedVm)
+    }
+
+    /// Starts the given VM instance and sets up the vsock connection with it.
+    /// Returns a `ServiceVm` instance.
+    /// This function is exposed for testing.
+    pub fn start_vm(vm: VmInstance, vm_type: VmType) -> Result<Self> {
+        // Sets up the vsock server on the host.
+        let vsock_listener = VsockListener::bind_with_cid_port(VMADDR_CID_HOST, vm_type.port())?;
+
+        // Starts the service VM.
+        vm.start().context("Failed to start service VM")?;
+        info!("Service VM started");
+
+        // Accepts the connection from the service VM.
+        // TODO(b/299427101): Introduce a timeout for the accept.
+        let (vsock_stream, peer_addr) = vsock_listener.accept().context("Failed to accept")?;
+        info!("Accepted connection {:?}", vsock_stream);
+        ensure!(
+            peer_addr.cid() == u32::try_from(vm.cid()).unwrap(),
+            "The CID of the peer address {} doesn't match the service VM CID {}",
+            peer_addr,
+            vm.cid()
+        );
+        vsock_stream.set_read_timeout(Some(READ_TIMEOUT))?;
+        vsock_stream.set_write_timeout(Some(WRITE_TIMEOUT))?;
+
+        Ok(Self { vsock_stream, vm })
+    }
+
+    /// Processes the request in the service VM.
+    pub fn process_request(&mut self, request: &Request) -> Result<Response> {
+        self.write_request(request)?;
+        self.read_response()
+    }
+
+    /// Sends the request to the service VM.
+    fn write_request(&mut self, request: &Request) -> Result<()> {
+        let mut buffer = BufWriter::with_capacity(WRITE_BUFFER_CAPACITY, &mut self.vsock_stream);
+        ciborium::into_writer(request, &mut buffer)?;
+        buffer.flush().context("Failed to flush the buffer")?;
+        info!("Sent request to the service VM.");
+        Ok(())
+    }
+
+    /// Reads the response from the service VM.
+    fn read_response(&mut self) -> Result<Response> {
+        let response: Response = ciborium::from_reader(&mut self.vsock_stream)
+            .context("Failed to read the response from the service VM")?;
+        info!("Received response from the service VM.");
+        Ok(response)
+    }
+}
+
+impl Drop for ServiceVm {
+    fn drop(&mut self) {
+        // Wait till the service VM finishes releasing all the resources.
+        match self.vm.wait_for_death_with_timeout(Duration::from_secs(10)) {
+            Some(e) => info!("Exit the service VM: {e:?}"),
+            None => warn!("Timed out waiting for service VM exit"),
+        }
+    }
+}
+
+fn vm_instance() -> Result<VmInstance> {
+    let virtmgr = vmclient::VirtualizationService::new().context("Failed to spawn VirtMgr")?;
+    let service = virtmgr.connect().context("Failed to connect to VirtMgr")?;
+    info!("Connected to VirtMgr for service VM");
+
+    let instance_img_path = Path::new(VIRT_DATA_DIR).join(INSTANCE_IMG_NAME);
+    let instance_img = instance_img(service.as_ref(), instance_img_path)?;
+    let writable_partitions = vec![Partition {
+        label: "vm-instance".to_owned(),
+        image: Some(instance_img),
+        writable: true,
+    }];
+    let rialto = File::open(RIALTO_PATH).context("Failed to open Rialto kernel binary")?;
+    let config = VirtualMachineConfig::RawConfig(VirtualMachineRawConfig {
+        name: String::from("Service VM"),
+        bootloader: Some(ParcelFileDescriptor::new(rialto)),
+        disks: vec![DiskImage { image: None, partitions: writable_partitions, writable: true }],
+        protectedVm: true,
+        memoryMib: MEMORY_MB,
+        cpuTopology: CpuTopology::ONE_CPU,
+        platformVersion: "~1.0".to_string(),
+        gdbPort: 0, // No gdb
+        ..Default::default()
+    });
+    let console_out = None;
+    let console_in = None;
+    let log = None;
+    let callback = None;
+    VmInstance::create(service.as_ref(), &config, console_out, console_in, log, callback)
+        .context("Failed to create service VM")
+}
+
+/// Returns the file descriptor of the instance image at the given path.
+/// This function is only exposed for testing.
+pub fn instance_img(
+    service: &dyn IVirtualizationService,
+    instance_img_path: PathBuf,
+) -> Result<ParcelFileDescriptor> {
+    if instance_img_path.exists() {
+        // TODO(b/298174584): Try to recover if the service VM is triggered by rkpd.
+        return Ok(OpenOptions::new()
+            .read(true)
+            .write(true)
+            .open(instance_img_path)
+            .map(ParcelFileDescriptor::new)?);
+    }
+    let instance_img = OpenOptions::new()
+        .create(true)
+        .read(true)
+        .write(true)
+        .open(instance_img_path)
+        .map(ParcelFileDescriptor::new)?;
+    service.initializeWritablePartition(
+        &instance_img,
+        INSTANCE_IMG_SIZE_BYTES,
+        PartitionType::ANDROID_VM_INSTANCE,
+    )?;
+    Ok(instance_img)
+}
diff --git a/virtualizationmanager/src/aidl.rs b/virtualizationmanager/src/aidl.rs
index 781d83f..e79cd5a 100644
--- a/virtualizationmanager/src/aidl.rs
+++ b/virtualizationmanager/src/aidl.rs
@@ -400,8 +400,9 @@
 
         // Check if partition images are labeled incorrectly. This is to prevent random images
         // which are not protected by the Android Verified Boot (e.g. bits downloaded by apps) from
-        // being loaded in a pVM. This applies to everything but the instance image in the raw config,
-        // and everything but the non-executable, generated partitions in the app config.
+        // being loaded in a pVM. This applies to everything but the instance image in the raw
+        // config, and everything but the non-executable, generated partitions in the app
+        // config.
         config
             .disks
             .iter()
@@ -989,10 +990,10 @@
 /// struct.
 #[derive(Debug, Default)]
 struct State {
-    /// The VMs which have been started. When VMs are started a weak reference is added to this list
-    /// while a strong reference is returned to the caller over Binder. Once all copies of the
-    /// Binder client are dropped the weak reference here will become invalid, and will be removed
-    /// from the list opportunistically the next time `add_vm` is called.
+    /// The VMs which have been started. When VMs are started a weak reference is added to this
+    /// list while a strong reference is returned to the caller over Binder. Once all copies of
+    /// the Binder client are dropped the weak reference here will become invalid, and will be
+    /// removed from the list opportunistically the next time `add_vm` is called.
     vms: Vec<Weak<VmInstance>>,
 }
 
diff --git a/virtualizationservice/Android.bp b/virtualizationservice/Android.bp
index ac9c1df..c00445d 100644
--- a/virtualizationservice/Android.bp
+++ b/virtualizationservice/Android.bp
@@ -41,6 +41,8 @@
         "libvsock",
         "libserde",
         "libserde_xml_rs",
+        "libservice_vm_comm",
+        "libservice_vm_manager",
     ],
     apex_available: ["com.android.virt"],
 }
diff --git a/virtualizationservice/src/main.rs b/virtualizationservice/src/main.rs
index cdb3ac9..9778599 100644
--- a/virtualizationservice/src/main.rs
+++ b/virtualizationservice/src/main.rs
@@ -18,7 +18,6 @@
 mod atom;
 mod remote_provisioning;
 mod rkpvm;
-mod service_vm;
 
 use crate::aidl::{
     remove_temporary_dir, BINDER_SERVICE_IDENTIFIER, TEMPORARY_DIRECTORY,
diff --git a/virtualizationservice/src/rkpvm.rs b/virtualizationservice/src/rkpvm.rs
index bb05edd..2c9230b 100644
--- a/virtualizationservice/src/rkpvm.rs
+++ b/virtualizationservice/src/rkpvm.rs
@@ -16,19 +16,18 @@
 //! The RKP VM will be recognized and attested by the RKP server periodically and
 //! serves as a trusted platform to attest a client VM.
 
-use crate::service_vm;
-use anyhow::{anyhow, Result};
-use log::info;
-use std::time::Duration;
+use anyhow::{bail, Context, Result};
+use service_vm_comm::{Request, Response};
+use service_vm_manager::ServiceVm;
 
 pub(crate) fn request_certificate(csr: &[u8]) -> Result<Vec<u8>> {
-    let vm = service_vm::start()?;
+    let mut vm = ServiceVm::start()?;
 
-    // TODO(b/274441673): The host can send the CSR to the RKP VM for attestation.
-    // Wait for VM to finish.
-    vm.wait_for_death_with_timeout(Duration::from_secs(10))
-        .ok_or_else(|| anyhow!("Timed out waiting for VM exit"))?;
-
-    info!("service_vm: Finished getting the certificate");
-    Ok([b"Return: ", csr].concat())
+    // TODO(b/271275206): Send the correct request type with client VM's
+    // information to be attested.
+    let request = Request::Reverse(csr.to_vec());
+    match vm.process_request(&request).context("Failed to process request")? {
+        Response::Reverse(cert) => Ok(cert),
+        _ => bail!("Incorrect response type"),
+    }
 }
diff --git a/virtualizationservice/src/service_vm.rs b/virtualizationservice/src/service_vm.rs
deleted file mode 100644
index 5a1744f..0000000
--- a/virtualizationservice/src/service_vm.rs
+++ /dev/null
@@ -1,102 +0,0 @@
-// Copyright 2023, The Android Open Source Project
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-//! Service VM.
-
-use android_system_virtualizationservice::{
-    aidl::android::system::virtualizationservice::{
-        CpuTopology::CpuTopology, DiskImage::DiskImage,
-        IVirtualizationService::IVirtualizationService, Partition::Partition,
-        PartitionType::PartitionType, VirtualMachineConfig::VirtualMachineConfig,
-        VirtualMachineRawConfig::VirtualMachineRawConfig,
-    },
-    binder::ParcelFileDescriptor,
-};
-use anyhow::{Context, Result};
-use log::info;
-use std::fs::{File, OpenOptions};
-use std::path::Path;
-use vmclient::VmInstance;
-
-const VIRT_DATA_DIR: &str = "/data/misc/apexdata/com.android.virt";
-const RIALTO_PATH: &str = "/apex/com.android.virt/etc/rialto.bin";
-const INSTANCE_IMG_NAME: &str = "service_vm_instance.img";
-const INSTANCE_IMG_SIZE_BYTES: i64 = 1 << 20; // 1MB
-const MEMORY_MB: i32 = 300;
-
-/// Starts the service VM and returns its instance.
-/// The same instance image is used for different VMs.
-/// TODO(b/278858244): Allow only one service VM running at each time.
-pub fn start() -> Result<VmInstance> {
-    let virtmgr = vmclient::VirtualizationService::new().context("Failed to spawn VirtMgr")?;
-    let service = virtmgr.connect().context("Failed to connect to VirtMgr")?;
-    info!("Connected to VirtMgr for service VM");
-
-    let vm = vm_instance(service.as_ref())?;
-
-    vm.start().context("Failed to start service VM")?;
-    info!("Service VM started");
-    Ok(vm)
-}
-
-fn vm_instance(service: &dyn IVirtualizationService) -> Result<VmInstance> {
-    let instance_img = instance_img(service)?;
-    let writable_partitions = vec![Partition {
-        label: "vm-instance".to_owned(),
-        image: Some(instance_img),
-        writable: true,
-    }];
-    let rialto = File::open(RIALTO_PATH).context("Failed to open Rialto kernel binary")?;
-    let config = VirtualMachineConfig::RawConfig(VirtualMachineRawConfig {
-        name: String::from("Service VM"),
-        bootloader: Some(ParcelFileDescriptor::new(rialto)),
-        disks: vec![DiskImage { image: None, partitions: writable_partitions, writable: true }],
-        protectedVm: true,
-        memoryMib: MEMORY_MB,
-        cpuTopology: CpuTopology::ONE_CPU,
-        platformVersion: "~1.0".to_string(),
-        gdbPort: 0, // No gdb
-        ..Default::default()
-    });
-    let console_out = None;
-    let console_in = None;
-    let log = None;
-    let callback = None;
-    VmInstance::create(service, &config, console_out, console_in, log, callback)
-        .context("Failed to create service VM")
-}
-
-fn instance_img(service: &dyn IVirtualizationService) -> Result<ParcelFileDescriptor> {
-    let instance_img_path = Path::new(VIRT_DATA_DIR).join(INSTANCE_IMG_NAME);
-    if instance_img_path.exists() {
-        // TODO(b/298174584): Try to recover if the service VM is triggered by rkpd.
-        return Ok(OpenOptions::new()
-            .read(true)
-            .write(true)
-            .open(instance_img_path)
-            .map(ParcelFileDescriptor::new)?);
-    }
-    let instance_img = OpenOptions::new()
-        .create(true)
-        .read(true)
-        .write(true)
-        .open(instance_img_path)
-        .map(ParcelFileDescriptor::new)?;
-    service.initializeWritablePartition(
-        &instance_img,
-        INSTANCE_IMG_SIZE_BYTES,
-        PartitionType::ANDROID_VM_INSTANCE,
-    )?;
-    Ok(instance_img)
-}