|
1 | 1 | use rusqlite::OptionalExtension; |
2 | 2 |
|
3 | 3 | use crate::{ |
4 | | - CryptoKeystoreError, MissingKeyErrorKind, |
| 4 | + CryptoKeystoreError, CryptoKeystoreResult, Entity, EntityBase, EntityTransactionExt, UniqueEntity, |
5 | 5 | connection::{KeystoreDatabaseConnection, TransactionWrapper}, |
6 | | - entities::{Entity, EntityBase, EntityFindParams, EntityTransactionExt, ProteusIdentity, StringEntityId}, |
| 6 | + entities::ProteusIdentity, |
7 | 7 | }; |
8 | 8 |
|
9 | | -#[async_trait::async_trait] |
10 | | -impl Entity for ProteusIdentity { |
11 | | - fn id_raw(&self) -> &[u8] { |
12 | | - Self::ID |
13 | | - } |
14 | | - |
15 | | - async fn find_all( |
16 | | - conn: &mut Self::ConnectionType, |
17 | | - _params: EntityFindParams, |
18 | | - ) -> crate::CryptoKeystoreResult<Vec<Self>> { |
19 | | - let mut res = vec![]; |
20 | | - if let Some(identity) = Self::find_one(conn, &StringEntityId::default()).await? { |
21 | | - res.push(identity); |
22 | | - } |
| 9 | +impl EntityBase for ProteusIdentity { |
| 10 | + type ConnectionType = KeystoreDatabaseConnection; |
| 11 | + type AutoGeneratedFields = (); |
| 12 | + const COLLECTION_NAME: &'static str = "proteus_identities"; |
23 | 13 |
|
24 | | - Ok(res) |
| 14 | + fn to_transaction_entity(self) -> crate::transaction::dynamic_dispatch::Entity { |
| 15 | + crate::transaction::dynamic_dispatch::Entity::ProteusIdentity(self) |
25 | 16 | } |
| 17 | +} |
26 | 18 |
|
27 | | - async fn find_one( |
28 | | - conn: &mut Self::ConnectionType, |
29 | | - _id: &StringEntityId, |
30 | | - ) -> crate::CryptoKeystoreResult<Option<Self>> { |
31 | | - let mut conn = conn.conn().await; |
32 | | - let transaction = conn.transaction()?; |
| 19 | +impl UniqueEntity for ProteusIdentity { |
| 20 | + const KEY: () = (); |
| 21 | +} |
33 | 22 |
|
34 | | - let mut row_id: Option<i64> = transaction |
35 | | - .query_row( |
36 | | - "SELECT rowid FROM proteus_identities ORDER BY rowid ASC LIMIT 1", |
37 | | - [], |
38 | | - |r| r.get(0), |
39 | | - ) |
40 | | - .optional()?; |
| 23 | +#[async_trait::async_trait] |
| 24 | +impl Entity for ProteusIdentity { |
| 25 | + /// Each distinct [`PrimaryKey`] uniquely identifies either 0 or 1 instance. |
| 26 | + /// |
| 27 | + /// This constraint should be enforced at the DB level. |
| 28 | + type PrimaryKey = (); |
| 29 | + |
| 30 | + /// Get this entity's primary key. |
| 31 | + fn primary_key(&self) -> Self::PrimaryKey { |
| 32 | + () |
| 33 | + } |
41 | 34 |
|
42 | | - let row_id = if let Some(rowid) = row_id.take() { |
43 | | - rowid |
44 | | - } else { |
| 35 | + /// Get an entity by its primary key. |
| 36 | + /// |
| 37 | + /// The type signature here is somewhat complicated, but it breaks down simply: if our primary key is something |
| 38 | + /// like `Vec<u8>`, we want to be able to use this method even if what we have on hand is `&[u8]`. |
| 39 | + /// |
| 40 | + /// Actually ignoring the key here is unusual, but acceptable for a unique entity. |
| 41 | + async fn get(conn: &mut Self::ConnectionType, _key: &()) -> CryptoKeystoreResult<Option<Self>> { |
| 42 | + let conn = conn.conn().await; |
| 43 | + // this gets the oldest identity, and is retained behavior from a previous implementation: |
| 44 | + // it used to save additional bonus identities without error |
| 45 | + let mut statement = conn.prepare_cached("SELECT pk, sk FROM proteus_identities ORDER BY rowid ASC LIMIT 1")?; |
| 46 | + let Some(identity) = statement |
| 47 | + .query_row([], |row| { |
| 48 | + let identity = ProteusIdentity { |
| 49 | + pk: row.get("pk")?, |
| 50 | + sk: row.get("sk")?, |
| 51 | + }; |
| 52 | + Ok(identity) |
| 53 | + }) |
| 54 | + .optional()? |
| 55 | + else { |
45 | 56 | return Ok(None); |
46 | 57 | }; |
47 | 58 |
|
48 | | - use std::io::Read as _; |
49 | | - let mut blob = transaction.blob_open(rusqlite::MAIN_DB, "proteus_identities", "pk", row_id, true)?; |
50 | | - if blob.len() != Self::PK_KEY_SIZE { |
| 59 | + if identity.pk.len() != Self::PK_KEY_SIZE { |
51 | 60 | return Err(CryptoKeystoreError::InvalidKeySize { |
52 | 61 | expected: Self::PK_KEY_SIZE, |
53 | | - actual: blob.len(), |
| 62 | + actual: identity.pk.len(), |
54 | 63 | key: "pk", |
55 | 64 | }); |
56 | 65 | } |
57 | | - let mut pk = Vec::with_capacity(blob.len()); |
58 | | - blob.read_to_end(&mut pk)?; |
59 | | - blob.close()?; |
60 | | - |
61 | | - let mut blob = transaction.blob_open(rusqlite::MAIN_DB, "proteus_identities", "sk", row_id, true)?; |
62 | | - if blob.len() != Self::SK_KEY_SIZE { |
| 66 | + if identity.sk.len() != Self::SK_KEY_SIZE { |
63 | 67 | return Err(CryptoKeystoreError::InvalidKeySize { |
64 | 68 | expected: Self::SK_KEY_SIZE, |
65 | | - actual: blob.len(), |
| 69 | + actual: identity.sk.len(), |
66 | 70 | key: "sk", |
67 | 71 | }); |
68 | 72 | } |
69 | | - let mut sk = Vec::with_capacity(blob.len()); |
70 | | - blob.read_to_end(&mut sk)?; |
71 | | - blob.close()?; |
72 | 73 |
|
73 | | - Ok(Some(Self { pk, sk })) |
| 74 | + Ok(Some(identity)) |
74 | 75 | } |
75 | 76 |
|
76 | | - async fn count(conn: &mut Self::ConnectionType) -> crate::CryptoKeystoreResult<usize> { |
| 77 | + /// Count the number of entities of this type in the database. |
| 78 | + async fn count(conn: &mut Self::ConnectionType) -> CryptoKeystoreResult<u32> { |
77 | 79 | let conn = conn.conn().await; |
78 | | - let count = conn.query_row("SELECT COUNT(*) FROM proteus_identities", [], |r| r.get(0))?; |
| 80 | + let mut statement = conn.prepare_cached("SELECT COUNT(*) FROM proteus_identities")?; |
| 81 | + let count = statement.query_one([], |row| row.get(0))?; |
79 | 82 | // This should always be less or equal 1 |
80 | 83 | debug_assert!(count <= 1); |
81 | 84 | Ok(count) |
82 | 85 | } |
83 | | -} |
84 | 86 |
|
85 | | -#[async_trait::async_trait] |
86 | | -impl EntityBase for ProteusIdentity { |
87 | | - type ConnectionType = KeystoreDatabaseConnection; |
88 | | - type AutoGeneratedFields = (); |
89 | | - const COLLECTION_NAME: &'static str = "proteus_identities"; |
90 | | - |
91 | | - fn to_missing_key_err_kind() -> MissingKeyErrorKind { |
92 | | - MissingKeyErrorKind::ProteusIdentity |
93 | | - } |
94 | | - |
95 | | - fn to_transaction_entity(self) -> crate::transaction::dynamic_dispatch::Entity { |
96 | | - crate::transaction::dynamic_dispatch::Entity::ProteusIdentity(self) |
| 87 | + /// Retrieve all entities of this type from the database. |
| 88 | + async fn load_all(conn: &mut Self::ConnectionType) -> CryptoKeystoreResult<Vec<Self>> { |
| 89 | + Self::get(conn, &()) |
| 90 | + .await |
| 91 | + .map(|optional| optional.into_iter().collect()) |
97 | 92 | } |
98 | 93 | } |
99 | 94 |
|
100 | 95 | #[async_trait::async_trait] |
101 | | -impl EntityTransactionExt for ProteusIdentity { |
102 | | - async fn save(&self, transaction: &TransactionWrapper<'_>) -> crate::CryptoKeystoreResult<()> { |
103 | | - use rusqlite::ToSql as _; |
104 | | - transaction.execute( |
105 | | - "INSERT INTO proteus_identities (sk, pk) VALUES (?, ?)", |
106 | | - [ |
107 | | - rusqlite::blob::ZeroBlob(self.sk.len() as i32).to_sql()?, |
108 | | - rusqlite::blob::ZeroBlob(self.pk.len() as i32).to_sql()?, |
109 | | - ], |
110 | | - )?; |
111 | | - |
112 | | - let row_id = transaction.last_insert_rowid(); |
113 | | - |
114 | | - use std::io::Write as _; |
115 | | - let mut blob = transaction.blob_open(rusqlite::MAIN_DB, "proteus_identities", "sk", row_id, false)?; |
116 | | - blob.write_all(&self.sk)?; |
117 | | - blob.close()?; |
118 | | - |
119 | | - let mut blob = transaction.blob_open(rusqlite::MAIN_DB, "proteus_identities", "pk", row_id, false)?; |
120 | | - blob.write_all(&self.pk)?; |
121 | | - blob.close()?; |
| 96 | +impl<'a> EntityTransactionExt<'a> for ProteusIdentity { |
| 97 | + type Transaction = TransactionWrapper<'a>; |
| 98 | + |
| 99 | + /// Use the transaction's interface to save this entity to the database |
| 100 | + async fn save(&self, tx: &Self::Transaction) -> CryptoKeystoreResult<()> { |
| 101 | + let mut statement = tx.prepare_cached("SELECT COUNT(*) FROM proteus_identities")?; |
| 102 | + let count = statement.query_one([], |row| row.get::<_, u32>(0))?; |
| 103 | + if count > 0 { |
| 104 | + return Err(CryptoKeystoreError::AlreadyExists("ProteusEntity")); |
| 105 | + } |
122 | 106 |
|
| 107 | + let mut statement = tx.prepare_cached("INSERT INTO proteus_identities (sk, pk) values (?, ?)")?; |
| 108 | + statement.execute([&self.sk, &self.pk])?; |
123 | 109 | Ok(()) |
124 | 110 | } |
125 | 111 |
|
126 | | - async fn delete_fail_on_missing_id( |
127 | | - transaction: &TransactionWrapper<'_>, |
128 | | - _id: StringEntityId<'_>, |
129 | | - ) -> crate::CryptoKeystoreResult<()> { |
130 | | - let row_id = transaction.query_row( |
131 | | - "SELECT rowid FROM proteus_identities ORDER BY rowid ASC LIMIT 1", |
132 | | - [], |
133 | | - |r| r.get::<_, i64>(0), |
134 | | - )?; |
135 | | - use rusqlite::ToSql as _; |
136 | | - transaction.execute("DELETE FROM proteus_identities WHERE rowid = ?", [row_id.to_sql()?])?; |
137 | | - |
138 | | - Ok(()) |
| 112 | + /// Use the transaction's inteface to delete this entity from the database. |
| 113 | + /// |
| 114 | + /// Returns `true` if at least one entity was deleted, or `false` if the id was not found in the database. |
| 115 | + async fn delete(tx: &Self::Transaction, _id: &()) -> CryptoKeystoreResult<bool> { |
| 116 | + let mut statement = tx.prepare_cached("DELETE FROM proteus_identities")?; |
| 117 | + let affected = statement.execute([])?; |
| 118 | + Ok(affected > 0) |
139 | 119 | } |
140 | 120 | } |
0 commit comments