Super encrypted keys

This CL implements super encryption of auth bound keys.

Bug: 173545997
Test: TBD
Change-Id: I71ca59803797d819a717dbd080550a61d88fe1c3
diff --git a/keystore2/src/database.rs b/keystore2/src/database.rs
index 6cdbc3e..2663c6e 100644
--- a/keystore2/src/database.rs
+++ b/keystore2/src/database.rs
@@ -144,7 +144,7 @@
     fn store_in_db(&self, key_id: i64, tx: &Transaction) -> Result<()> {
         let mut stmt = tx
             .prepare(
-                "INSERT into persistent.keymetadata (keyentryid, tag, data)
+                "INSERT or REPLACE INTO persistent.keymetadata (keyentryid, tag, data)
                     VALUES (?, ?, ?);",
             )
             .context("In KeyMetaData::store_in_db: Failed to prepare statement.")?;
@@ -653,6 +653,10 @@
     pub fn pure_cert(&self) -> bool {
         self.pure_cert
     }
+    /// Consumes this key entry and extracts the keyparameters and metadata from it.
+    pub fn into_key_parameters_and_metadata(self) -> (Vec<KeyParameter>, KeyMetaData) {
+        (self.parameters, self.metadata)
+    }
 }
 
 /// Indicates the sub component of a key entry for persistent storage.
@@ -915,7 +919,8 @@
             "CREATE TABLE IF NOT EXISTS persistent.keymetadata (
                      keyentryid INTEGER,
                      tag INTEGER,
-                     data ANY);",
+                     data ANY,
+                     UNIQUE (keyentryid, tag));",
             NO_PARAMS,
         )
         .context("Failed to initialize \"keymetadata\" table.")?;
@@ -1131,10 +1136,11 @@
                 tx.execute(
                     "INSERT into persistent.keyentry
                             (id, key_type, domain, namespace, alias, state, km_uuid)
-                            VALUES(?, ?, NULL, ?, ?, ?, ?);",
+                            VALUES(?, ?, ?, ?, ?, ?, ?);",
                     params![
                         id,
                         KeyType::Super,
+                        Domain::APP.0,
                         user_id,
                         Self::USER_SUPER_KEY_ALIAS,
                         KeyLifeCycle::Live,
@@ -2033,7 +2039,7 @@
             .prepare(
                 "SELECT id FROM persistent.keyentry
                     WHERE
-                    key_type =  ?
+                    key_type = ?
                     AND domain = ?
                     AND namespace = ?
                     AND alias = ?
@@ -2861,8 +2867,10 @@
     where
         F: Fn(&Uuid, &[u8]) -> Result<()> + Send + 'static,
     {
+        let super_key = Arc::new(SuperKeyManager::new());
+
         let gc_db = KeystoreDB::new(path, None).expect("Failed to open test gc db_connection.");
-        let gc = Gc::new_init_with(Default::default(), move || (Box::new(cb), gc_db));
+        let gc = Gc::new_init_with(Default::default(), move || (Box::new(cb), gc_db, super_key));
 
         KeystoreDB::new(path, Some(gc))
     }
@@ -4719,6 +4727,9 @@
             SuperKeyManager::encrypt_with_password(&super_key, &pw)?;
         db.store_super_key(1, &(&encrypted_super_key, &metadata))?;
 
+        //check if super key exists
+        assert!(db.key_exists(Domain::APP, 1, "USER_SUPER_KEY", KeyType::Super)?);
+
         //load the super key from the database
         let tx = db.conn.transaction_with_behavior(TransactionBehavior::Immediate)?;
         let key_descriptor = KeyDescriptor {
diff --git a/keystore2/src/enforcements.rs b/keystore2/src/enforcements.rs
index 9c3bc89..cc59c32 100644
--- a/keystore2/src/enforcements.rs
+++ b/keystore2/src/enforcements.rs
@@ -26,7 +26,10 @@
 use android_hardware_security_secureclock::aidl::android::hardware::security::secureclock::{
     ISecureClock::ISecureClock, TimeStampToken::TimeStampToken,
 };
-use android_system_keystore2::aidl::android::system::keystore2::OperationChallenge::OperationChallenge;
+use android_system_keystore2::aidl::android::system::keystore2::{
+    IKeystoreSecurityLevel::KEY_FLAG_AUTH_BOUND_WITHOUT_CRYPTOGRAPHIC_LSKF_BINDING,
+    OperationChallenge::OperationChallenge,
+};
 use android_system_keystore2::binder::Strong;
 use anyhow::{Context, Result};
 use std::sync::{
@@ -744,6 +747,19 @@
     fn register_op_auth_receiver(&self, challenge: i64, recv: TokenReceiver) {
         self.op_auth_map.add_receiver(challenge, recv);
     }
+
+    /// Given the set of key parameters and flags, check if super encryption is required.
+    pub fn super_encryption_required(key_parameters: &[KeyParameter], flags: Option<i32>) -> bool {
+        let auth_bound = key_parameters.iter().any(|kp| kp.get_tag() == Tag::USER_SECURE_ID);
+
+        let skip_lskf_binding = if let Some(flags) = flags {
+            (flags & KEY_FLAG_AUTH_BOUND_WITHOUT_CRYPTOGRAPHIC_LSKF_BINDING) != 0
+        } else {
+            false
+        };
+
+        auth_bound && !skip_lskf_binding
+    }
 }
 
 impl Default for Enforcements {
diff --git a/keystore2/src/gc.rs b/keystore2/src/gc.rs
index b5b1c6c..6cc0f27 100644
--- a/keystore2/src/gc.rs
+++ b/keystore2/src/gc.rs
@@ -21,6 +21,7 @@
 use crate::{
     async_task,
     database::{KeystoreDB, Uuid},
+    super_key::SuperKeyManager,
 };
 use anyhow::{Context, Result};
 use async_task::AsyncTask;
@@ -37,19 +38,23 @@
     /// time a garbage collector was initialized with the given AsyncTask instance.
     pub fn new_init_with<F>(async_task: Arc<AsyncTask>, init: F) -> Self
     where
-        F: FnOnce() -> (Box<dyn Fn(&Uuid, &[u8]) -> Result<()> + Send + 'static>, KeystoreDB)
-            + Send
+        F: FnOnce() -> (
+                Box<dyn Fn(&Uuid, &[u8]) -> Result<()> + Send + 'static>,
+                KeystoreDB,
+                Arc<SuperKeyManager>,
+            ) + Send
             + 'static,
     {
         let weak_at = Arc::downgrade(&async_task);
         // Initialize the task's shelf.
         async_task.queue_hi(move |shelf| {
-            let (invalidate_key, db) = init();
+            let (invalidate_key, db, super_key) = init();
             shelf.get_or_put_with(|| GcInternal {
                 blob_id_to_delete: None,
                 invalidate_key,
                 db,
                 async_task: weak_at,
+                super_key,
             });
         });
         Self { async_task }
@@ -68,6 +73,7 @@
     invalidate_key: Box<dyn Fn(&Uuid, &[u8]) -> Result<()> + Send + 'static>,
     db: KeystoreDB,
     async_task: std::sync::Weak<AsyncTask>,
+    super_key: Arc<SuperKeyManager>,
 }
 
 impl GcInternal {
@@ -91,6 +97,10 @@
             // (At this time keys may get deleted without having the super encryption
             // key in this case we can only delete the key from the database.)
             if let Some(uuid) = blob_metadata.km_uuid() {
+                let blob = self
+                    .super_key
+                    .unwrap_key_if_required(&blob_metadata, &blob)
+                    .context("In process_one_key: Trying to unwrap to-be-deleted blob.")?;
                 (self.invalidate_key)(&uuid, &*blob)
                     .context("In process_one_key: Trying to invalidate key.")?;
             }
diff --git a/keystore2/src/globals.rs b/keystore2/src/globals.rs
index 3037a03..83d381d 100644
--- a/keystore2/src/globals.rs
+++ b/keystore2/src/globals.rs
@@ -60,6 +60,7 @@
             }),
             KeystoreDB::new(&DB_PATH.lock().expect("Could not get the database directory."), None)
                 .expect("Failed to open database."),
+            SUPER_KEY.clone(),
         )
     });
 
diff --git a/keystore2/src/security_level.rs b/keystore2/src/security_level.rs
index c2170b6..23a3c35 100644
--- a/keystore2/src/security_level.rs
+++ b/keystore2/src/security_level.rs
@@ -31,9 +31,11 @@
     KeyMetadata::KeyMetadata, KeyParameters::KeyParameters,
 };
 
-use crate::globals::ENFORCEMENTS;
+use crate::database::{CertificateInfo, KeyIdGuard};
+use crate::globals::{DB, ENFORCEMENTS, SUPER_KEY};
 use crate::key_parameter::KeyParameter as KsKeyParam;
 use crate::key_parameter::KeyParameterValue as KsKeyParamValue;
+use crate::super_key::{KeyBlob, SuperKeyManager};
 use crate::utils::{check_key_permission, uid_to_android_user, Asp};
 use crate::{
     database::{
@@ -45,10 +47,6 @@
     permission::KeyPerm,
 };
 use crate::{
-    database::{CertificateInfo, KeyIdGuard},
-    globals::DB,
-};
-use crate::{
     error::{self, map_km_error, map_or_log_err, Error, ErrorCode},
     utils::key_characteristics_to_internal,
 };
@@ -98,6 +96,7 @@
         key: KeyDescriptor,
         creation_result: KeyCreationResult,
         user_id: u32,
+        flags: Option<i32>,
     ) -> Result<KeyMetadata> {
         let KeyCreationResult {
             keyBlob: key_blob,
@@ -130,6 +129,20 @@
             SecurityLevel::SOFTWARE,
         ));
 
+        let (key_blob, mut blob_metadata) = DB
+            .with(|db| {
+                SuperKeyManager::handle_super_encryption_on_key_init(
+                    &mut db.borrow_mut(),
+                    &SUPER_KEY,
+                    &(key.domain),
+                    &key_parameters,
+                    flags,
+                    user_id,
+                    &key_blob,
+                )
+            })
+            .context("In store_new_key. Failed to handle super encryption.")?;
+
         let creation_date = DateTime::now().context("Trying to make creation time.")?;
 
         let key = match key.domain {
@@ -140,7 +153,6 @@
                 .with::<_, Result<KeyDescriptor>>(|db| {
                     let mut key_metadata = KeyMetaData::new();
                     key_metadata.add(KeyMetaEntry::CreationDate(creation_date));
-                    let mut blob_metadata = BlobMetaData::new();
                     blob_metadata.add(BlobMetaEntry::KmUuid(self.km_uuid));
 
                     let mut db = db.borrow_mut();
@@ -200,7 +212,7 @@
                     },
                     None,
                     None,
-                    None,
+                    BlobMetaData::new(),
                 )
             }
             _ => {
@@ -227,7 +239,7 @@
                     &scoping_blob,
                     Some((key_id_guard.id(), key_entry.into_key_parameters())),
                     Some(key_id_guard),
-                    Some(blob_metadata),
+                    blob_metadata,
                 )
             }
         };
@@ -256,6 +268,12 @@
 
         let immediate_hat = immediate_hat.unwrap_or_default();
 
+        let user_id = uid_to_android_user(caller_uid);
+
+        let km_blob = SUPER_KEY
+            .unwrap_key_if_required(&blob_metadata, km_blob)
+            .context("In create_operation. Failed to handle super encryption.")?;
+
         let km_dev: Strong<dyn IKeyMintDevice> = self
             .keymint
             .get_interface()
@@ -265,7 +283,7 @@
             .upgrade_keyblob_if_required_with(
                 &*km_dev,
                 key_id_guard,
-                &(km_blob, blob_metadata.as_ref()),
+                &(&km_blob, &blob_metadata),
                 &operation_parameters,
                 |blob| loop {
                     match map_km_error(km_dev.begin(
@@ -393,7 +411,7 @@
             .context("In generate_key: While generating Key")?;
 
         let user_id = uid_to_android_user(caller_uid);
-        self.store_new_key(key, creation_result, user_id).context("In generate_key.")
+        self.store_new_key(key, creation_result, user_id, Some(flags)).context("In generate_key.")
     }
 
     fn import_key(
@@ -448,7 +466,7 @@
             .context("In import_key: Trying to call importKey")?;
 
         let user_id = uid_to_android_user(caller_uid);
-        self.store_new_key(key, creation_result, user_id).context("In import_key.")
+        self.store_new_key(key, creation_result, user_id, Some(flags)).context("In import_key.")
     }
 
     fn import_wrapped_key(
@@ -482,6 +500,8 @@
         }
 
         let caller_uid = ThreadState::get_calling_uid();
+        let user_id = uid_to_android_user(caller_uid);
+
         let key = match key.domain {
             Domain::APP => KeyDescriptor {
                 domain: key.domain,
@@ -501,7 +521,7 @@
         // Import_wrapped_key requires the rebind permission for the new key.
         check_key_permission(KeyPerm::rebind(), &key, &None).context("In import_wrapped_key.")?;
 
-        let (wrapping_key_id_guard, wrapping_key_entry) = DB
+        let (wrapping_key_id_guard, mut wrapping_key_entry) = DB
             .with(|db| {
                 db.borrow_mut().load_key_entry(
                     &wrapping_key,
@@ -512,15 +532,16 @@
                 )
             })
             .context("Failed to load wrapping key.")?;
-        let (wrapping_key_blob, wrapping_blob_metadata) = match wrapping_key_entry.key_blob_info() {
-            Some((blob, metadata)) => (blob, metadata),
-            None => {
-                return Err(error::Error::sys()).context(concat!(
-                    "No km_blob after successfully loading key.",
-                    " This should never happen."
-                ))
-            }
-        };
+
+        let (wrapping_key_blob, wrapping_blob_metadata) = wrapping_key_entry
+            .take_key_blob_info()
+            .ok_or_else(error::Error::sys)
+            .context("No km_blob after successfully loading key. This should never happen.")?;
+
+        let wrapping_key_blob =
+            SUPER_KEY.unwrap_key_if_required(&wrapping_blob_metadata, &wrapping_key_blob).context(
+                "In import_wrapped_key. Failed to handle super encryption for wrapping key.",
+            )?;
 
         // km_dev.importWrappedKey does not return a certificate chain.
         // TODO Do we assume that all wrapped keys are symmetric?
@@ -549,7 +570,7 @@
             .upgrade_keyblob_if_required_with(
                 &*km_dev,
                 Some(wrapping_key_id_guard),
-                &(&wrapping_key_blob, Some(&wrapping_blob_metadata)),
+                &(&wrapping_key_blob, &wrapping_blob_metadata),
                 &[],
                 |wrapping_blob| {
                     let creation_result = map_km_error(km_dev.importWrappedKey(
@@ -565,8 +586,7 @@
             )
             .context("In import_wrapped_key.")?;
 
-        let user_id = uid_to_android_user(caller_uid);
-        self.store_new_key(key, creation_result, user_id)
+        self.store_new_key(key, creation_result, user_id, None)
             .context("In import_wrapped_key: Trying to store the new key.")
     }
 
@@ -574,7 +594,7 @@
         &self,
         km_dev: &dyn IKeyMintDevice,
         key_id_guard: Option<KeyIdGuard>,
-        blob_info: &(&[u8], Option<&BlobMetaData>),
+        blob_info: &(&KeyBlob, &BlobMetaData),
         params: &[KeyParameter],
         f: F,
     ) -> Result<(T, Option<Vec<u8>>)>
@@ -585,13 +605,26 @@
             Err(Error::Km(ErrorCode::KEY_REQUIRES_UPGRADE)) => {
                 let upgraded_blob = map_km_error(km_dev.upgradeKey(blob_info.0, params))
                     .context("In upgrade_keyblob_if_required_with: Upgrade failed.")?;
+
+                let (upgraded_blob_to_be_stored, blob_metadata) =
+                    SuperKeyManager::reencrypt_on_upgrade_if_required(blob_info.0, &upgraded_blob)
+                        .context(
+                        "In upgrade_keyblob_if_required_with: Failed to handle super encryption.",
+                    )?;
+
+                let mut blob_metadata = blob_metadata.unwrap_or_else(BlobMetaData::new);
+                if let Some(uuid) = blob_info.1.km_uuid() {
+                    blob_metadata.add(BlobMetaEntry::KmUuid(*uuid));
+                }
+
                 key_id_guard.map_or(Ok(()), |key_id_guard| {
                     DB.with(|db| {
-                        db.borrow_mut().set_blob(
+                        let mut db = db.borrow_mut();
+                        db.set_blob(
                             &key_id_guard,
                             SubComponentType::KEY_BLOB,
-                            Some(&upgraded_blob),
-                            blob_info.1,
+                            Some(&upgraded_blob_to_be_stored),
+                            Some(&blob_metadata),
                         )
                     })
                     .context(concat!(
diff --git a/keystore2/src/super_key.rs b/keystore2/src/super_key.rs
index 0aae232..2df5343 100644
--- a/keystore2/src/super_key.rs
+++ b/keystore2/src/super_key.rs
@@ -16,8 +16,8 @@
 
 use crate::{
     database::BlobMetaData, database::BlobMetaEntry, database::EncryptedBy, database::KeyEntry,
-    database::KeyType, database::KeystoreDB, error::Error, error::ResponseCode,
-    legacy_blob::LegacyBlobLoader,
+    database::KeyType, database::KeystoreDB, enforcements::Enforcements, error::Error,
+    error::ResponseCode, key_parameter::KeyParameter, legacy_blob::LegacyBlobLoader,
 };
 use android_system_keystore2::aidl::android::system::keystore2::Domain::Domain;
 use anyhow::{Context, Result};
@@ -25,6 +25,7 @@
     aes_gcm_decrypt, aes_gcm_encrypt, derive_key_from_password, generate_aes256_key, generate_salt,
     ZVec, AES_256_KEY_LENGTH,
 };
+use std::ops::Deref;
 use std::{
     collections::HashMap,
     sync::Arc,
@@ -168,12 +169,13 @@
     /// The function queries `metadata.encrypted_by()` to determine the encryption key.
     /// It then check if the required key is memory resident, and if so decrypts the
     /// blob.
-    pub fn unwrap_key(&self, blob: &[u8], metadata: &BlobMetaData) -> Result<ZVec> {
+    pub fn unwrap_key<'a>(&self, blob: &'a [u8], metadata: &BlobMetaData) -> Result<KeyBlob<'a>> {
         match metadata.encrypted_by() {
             Some(EncryptedBy::KeyId(key_id)) => match self.get_key(key_id) {
-                Some(key) => {
-                    Self::unwrap_key_with_key(blob, metadata, &key).context("In unwrap_key.")
-                }
+                Some(key) => Ok(KeyBlob::Sensitive(
+                    Self::unwrap_key_with_key(blob, metadata, &key).context("In unwrap_key.")?,
+                    SuperKey { key: key.clone(), id: *key_id },
+                )),
                 None => Err(Error::Rc(ResponseCode::LOCKED))
                     .context("In unwrap_key: Key is not usable until the user entered their LSKF."),
             },
@@ -329,6 +331,112 @@
         metadata.add(BlobMetaEntry::AeadTag(tag));
         Ok((encrypted_key, metadata))
     }
+
+    // Encrypt the given key blob with the user's super key, if the super key exists and the device
+    // is unlocked. If the super key exists and the device is locked, or LSKF is not setup,
+    // return error. Note that it is out of the scope of this function to check if super encryption
+    // is required. Such check should be performed before calling this function.
+    fn super_encrypt_on_key_init(
+        db: &mut KeystoreDB,
+        skm: &SuperKeyManager,
+        user_id: u32,
+        key_blob: &[u8],
+    ) -> Result<(Vec<u8>, BlobMetaData)> {
+        match UserState::get(db, skm, user_id)
+            .context("In super_encrypt. Failed to get user state.")?
+        {
+            UserState::LskfUnlocked(super_key) => {
+                Self::encrypt_with_super_key(key_blob, &super_key)
+                    .context("In super_encrypt_on_key_init. Failed to encrypt the key.")
+            }
+            UserState::LskfLocked => {
+                Err(Error::Rc(ResponseCode::LOCKED)).context("In super_encrypt. Device is locked.")
+            }
+            UserState::Uninitialized => Err(Error::Rc(ResponseCode::UNINITIALIZED))
+                .context("In super_encrypt. LSKF is not setup for the user."),
+        }
+    }
+
+    //Helper function to encrypt a key with the given super key. Callers should select which super
+    //key to be used. This is called when a key is super encrypted at its creation as well as at its
+    //upgrade.
+    fn encrypt_with_super_key(
+        key_blob: &[u8],
+        super_key: &SuperKey,
+    ) -> Result<(Vec<u8>, BlobMetaData)> {
+        let mut metadata = BlobMetaData::new();
+        let (encrypted_key, iv, tag) = aes_gcm_encrypt(key_blob, &(super_key.key))
+            .context("In encrypt_with_super_key: Failed to encrypt new super key.")?;
+        metadata.add(BlobMetaEntry::Iv(iv));
+        metadata.add(BlobMetaEntry::AeadTag(tag));
+        metadata.add(BlobMetaEntry::EncryptedBy(EncryptedBy::KeyId(super_key.id)));
+        Ok((encrypted_key, metadata))
+    }
+
+    /// Check if super encryption is required and if so, super-encrypt the key to be stored in
+    /// the database.
+    pub fn handle_super_encryption_on_key_init(
+        db: &mut KeystoreDB,
+        skm: &SuperKeyManager,
+        domain: &Domain,
+        key_parameters: &[KeyParameter],
+        flags: Option<i32>,
+        user_id: u32,
+        key_blob: &[u8],
+    ) -> Result<(Vec<u8>, BlobMetaData)> {
+        match (*domain, Enforcements::super_encryption_required(key_parameters, flags)) {
+            (Domain::APP, true) => Self::super_encrypt_on_key_init(db, skm, user_id, &key_blob)
+                .context(
+                    "In handle_super_encryption_on_key_init.
+                         Failed to super encrypt the key.",
+                ),
+            _ => Ok((key_blob.to_vec(), BlobMetaData::new())),
+        }
+    }
+
+    /// Check if a given key is super-encrypted, from its metadata. If so, unwrap the key using
+    /// the relevant super key.
+    pub fn unwrap_key_if_required<'a>(
+        &self,
+        metadata: &BlobMetaData,
+        key_blob: &'a [u8],
+    ) -> Result<KeyBlob<'a>> {
+        if Self::key_super_encrypted(&metadata) {
+            let unwrapped_key = self
+                .unwrap_key(key_blob, metadata)
+                .context("In unwrap_key_if_required. Error in unwrapping the key.")?;
+            Ok(unwrapped_key)
+        } else {
+            Ok(KeyBlob::Ref(key_blob))
+        }
+    }
+
+    /// Check if a given key needs re-super-encryption, from its KeyBlob type.
+    /// If so, re-super-encrypt the key and return a new set of metadata,
+    /// containing the new super encryption information.
+    pub fn reencrypt_on_upgrade_if_required<'a>(
+        key_blob_before_upgrade: &KeyBlob,
+        key_after_upgrade: &'a [u8],
+    ) -> Result<(KeyBlob<'a>, Option<BlobMetaData>)> {
+        match key_blob_before_upgrade {
+            KeyBlob::Sensitive(_, super_key) => {
+                let (key, metadata) = Self::encrypt_with_super_key(key_after_upgrade, super_key)
+                .context(
+                "In reencrypt_on_upgrade_if_required. Failed to re-super-encrypt key on key upgrade.",
+                )?;
+                Ok((KeyBlob::NonSensitive(key), Some(metadata)))
+            }
+            _ => Ok((KeyBlob::Ref(key_after_upgrade), None)),
+        }
+    }
+
+    // Helper function to decide if a key is super encrypted, given metadata.
+    fn key_super_encrypted(metadata: &BlobMetaData) -> bool {
+        if let Some(&EncryptedBy::KeyId(_)) = metadata.encrypted_by() {
+            return true;
+        }
+        false
+    }
 }
 
 /// This enum represents different states of the user's life cycle in the device.
@@ -414,3 +522,27 @@
         Ok(())
     }
 }
+
+/// This enum represents two states a Keymint Blob can be in, w.r.t super encryption.
+/// Sensitive variant represents a Keymint blob that is supposed to be super encrypted,
+/// but unwrapped during usage. Therefore, it has the super key along with the unwrapped key.
+/// Ref variant represents a Keymint blob that is not required to super encrypt or that is
+/// already super encrypted.
+pub enum KeyBlob<'a> {
+    Sensitive(ZVec, SuperKey),
+    NonSensitive(Vec<u8>),
+    Ref(&'a [u8]),
+}
+
+/// Deref returns a reference to the key material in both variants.
+impl<'a> Deref for KeyBlob<'a> {
+    type Target = [u8];
+
+    fn deref(&self) -> &Self::Target {
+        match self {
+            Self::Sensitive(key, _) => &key,
+            Self::NonSensitive(key) => &key,
+            Self::Ref(key) => key,
+        }
+    }
+}