1 /* This Source Code Form is subject to the terms of the Mozilla Public
2  * License, v. 2.0. If a copy of the MPL was not distributed with this
3  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
4 
5 use crate::error::*;
6 use rusqlite::{Connection, Transaction};
7 use serde::{ser::SerializeMap, Serialize, Serializer};
8 
9 use serde_json::{Map, Value as JsonValue};
10 use sql_support::{self, ConnExt};
11 
12 // These constants are defined by the chrome.storage.sync spec. We export them
13 // publicly from this module, then from the crate, so they wind up in the
14 // clients.
15 // Note the limits for `chrome.storage.sync` and `chrome.storage.local` are
16 // different, and these are from `.sync` - we'll have work to do if we end up
17 // wanting this to be used for `.local` too!
18 pub const SYNC_QUOTA_BYTES: usize = 102_400;
19 pub const SYNC_QUOTA_BYTES_PER_ITEM: usize = 8_192;
20 pub const SYNC_MAX_ITEMS: usize = 512;
21 // Note there are also constants for "operations per minute" etc, which aren't
22 // enforced here.
23 
24 type JsonMap = Map<String, JsonValue>;
25 
26 enum StorageChangeOp {
27     Clear,
28     Set(JsonValue),
29     SetWithoutQuota(JsonValue),
30 }
31 
get_from_db(conn: &Connection, ext_id: &str) -> Result<Option<JsonMap>>32 fn get_from_db(conn: &Connection, ext_id: &str) -> Result<Option<JsonMap>> {
33     Ok(
34         match conn.try_query_one::<String>(
35             "SELECT data FROM storage_sync_data
36              WHERE ext_id = :ext_id",
37             &[(":ext_id", &ext_id)],
38             true,
39         )? {
40             Some(s) => match serde_json::from_str(&s)? {
41                 JsonValue::Object(m) => Some(m),
42                 // we could panic here as it's theoretically impossible, but we
43                 // might as well treat it as not existing...
44                 _ => None,
45             },
46             None => None,
47         },
48     )
49 }
50 
save_to_db(tx: &Transaction<'_>, ext_id: &str, val: &StorageChangeOp) -> Result<()>51 fn save_to_db(tx: &Transaction<'_>, ext_id: &str, val: &StorageChangeOp) -> Result<()> {
52     // This function also handles removals. Either an empty map or explicit null
53     // is a removal. If there's a mirror record for this extension ID, then we
54     // must leave a tombstone behind for syncing.
55     let is_delete = match val {
56         StorageChangeOp::Clear => true,
57         StorageChangeOp::Set(JsonValue::Object(v)) => v.is_empty(),
58         StorageChangeOp::SetWithoutQuota(JsonValue::Object(v)) => v.is_empty(),
59         _ => false,
60     };
61     if is_delete {
62         let in_mirror = tx
63             .try_query_one(
64                 "SELECT EXISTS(SELECT 1 FROM storage_sync_mirror WHERE ext_id = :ext_id);",
65                 rusqlite::named_params! {
66                     ":ext_id": ext_id,
67                 },
68                 true,
69             )?
70             .unwrap_or_default();
71         if in_mirror {
72             log::trace!("saving data for '{}': leaving a tombstone", ext_id);
73             tx.execute_named_cached(
74                 "
75                 INSERT INTO storage_sync_data(ext_id, data, sync_change_counter)
76                 VALUES (:ext_id, NULL, 1)
77                 ON CONFLICT (ext_id) DO UPDATE
78                 SET data = NULL, sync_change_counter = sync_change_counter + 1",
79                 rusqlite::named_params! {
80                     ":ext_id": ext_id,
81                 },
82             )?;
83         } else {
84             log::trace!("saving data for '{}': removing the row", ext_id);
85             tx.execute_named_cached(
86                 "
87                 DELETE FROM storage_sync_data WHERE ext_id = :ext_id",
88                 rusqlite::named_params! {
89                     ":ext_id": ext_id,
90                 },
91             )?;
92         }
93     } else {
94         // Convert to bytes so we can enforce the quota if necessary.
95         let sval = match val {
96             StorageChangeOp::Set(v) => {
97                 let sv = v.to_string();
98                 if sv.len() > SYNC_QUOTA_BYTES {
99                     return Err(ErrorKind::QuotaError(QuotaReason::TotalBytes).into());
100                 }
101                 sv
102             }
103             StorageChangeOp::SetWithoutQuota(v) => v.to_string(),
104             StorageChangeOp::Clear => unreachable!(),
105         };
106 
107         log::trace!("saving data for '{}': writing", ext_id);
108         tx.execute_named_cached(
109             "INSERT INTO storage_sync_data(ext_id, data, sync_change_counter)
110                 VALUES (:ext_id, :data, 1)
111                 ON CONFLICT (ext_id) DO UPDATE
112                 set data=:data, sync_change_counter = sync_change_counter + 1",
113             rusqlite::named_params! {
114                 ":ext_id": ext_id,
115                 ":data": &sval,
116             },
117         )?;
118     }
119     Ok(())
120 }
121 
remove_from_db(tx: &Transaction<'_>, ext_id: &str) -> Result<()>122 fn remove_from_db(tx: &Transaction<'_>, ext_id: &str) -> Result<()> {
123     save_to_db(tx, ext_id, &StorageChangeOp::Clear)
124 }
125 
126 // This is a "helper struct" for the callback part of the chrome.storage spec,
127 // but shaped in a way to make it more convenient from the rust side of the
128 // world.
129 #[derive(Debug, Clone, PartialEq, Serialize)]
130 #[serde(rename_all = "camelCase")]
131 pub struct StorageValueChange {
132     #[serde(skip_serializing)]
133     pub key: String,
134     #[serde(skip_serializing_if = "Option::is_none")]
135     pub old_value: Option<JsonValue>,
136     #[serde(skip_serializing_if = "Option::is_none")]
137     pub new_value: Option<JsonValue>,
138 }
139 
140 // This is, largely, a helper so that this serializes correctly as per the
141 // chrome.storage.sync spec. If not for custom serialization it should just
142 // be a plain vec
143 #[derive(Debug, Default, Clone, PartialEq)]
144 pub struct StorageChanges {
145     changes: Vec<StorageValueChange>,
146 }
147 
148 impl StorageChanges {
new() -> Self149     pub fn new() -> Self {
150         Self::default()
151     }
152 
with_capacity(n: usize) -> Self153     pub fn with_capacity(n: usize) -> Self {
154         Self {
155             changes: Vec::with_capacity(n),
156         }
157     }
158 
is_empty(&self) -> bool159     pub fn is_empty(&self) -> bool {
160         self.changes.is_empty()
161     }
162 
push(&mut self, change: StorageValueChange)163     pub fn push(&mut self, change: StorageValueChange) {
164         self.changes.push(change)
165     }
166 }
167 
168 // and it serializes as a map.
169 impl Serialize for StorageChanges {
serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error> where S: Serializer,170     fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
171     where
172         S: Serializer,
173     {
174         let mut map = serializer.serialize_map(Some(self.changes.len()))?;
175         for change in &self.changes {
176             map.serialize_entry(&change.key, change)?;
177         }
178         map.end()
179     }
180 }
181 
182 // A helper to determine the size of a key/value combination from the
183 // perspective of quota and getBytesInUse().
get_quota_size_of(key: &str, v: &JsonValue) -> usize184 pub fn get_quota_size_of(key: &str, v: &JsonValue) -> usize {
185     // Reading the chrome docs literally re the quota, the length of the key
186     // is just the string len, but the value is the json val, as bytes.
187     key.len() + v.to_string().len()
188 }
189 
190 /// The implementation of `storage[.sync].set()`. On success this returns the
191 /// StorageChanges defined by the chrome API - it's assumed the caller will
192 /// arrange to deliver this to observers as defined in that API.
set(tx: &Transaction<'_>, ext_id: &str, val: JsonValue) -> Result<StorageChanges>193 pub fn set(tx: &Transaction<'_>, ext_id: &str, val: JsonValue) -> Result<StorageChanges> {
194     let val_map = match val {
195         JsonValue::Object(m) => m,
196         // Not clear what the error semantics should be yet. For now, pretend an empty map.
197         _ => Map::new(),
198     };
199 
200     let mut current = get_from_db(tx, ext_id)?.unwrap_or_default();
201 
202     let mut changes = StorageChanges::with_capacity(val_map.len());
203 
204     // iterate over the value we are adding/updating.
205     for (k, v) in val_map.into_iter() {
206         let old_value = current.remove(&k);
207         if current.len() >= SYNC_MAX_ITEMS {
208             return Err(ErrorKind::QuotaError(QuotaReason::MaxItems).into());
209         }
210         // Reading the chrome docs literally re the quota, the length of the key
211         // is just the string len, but the value is the json val, as bytes
212         if get_quota_size_of(&k, &v) > SYNC_QUOTA_BYTES_PER_ITEM {
213             return Err(ErrorKind::QuotaError(QuotaReason::ItemBytes).into());
214         }
215         let change = StorageValueChange {
216             key: k.clone(),
217             old_value,
218             new_value: Some(v.clone()),
219         };
220         changes.push(change);
221         current.insert(k, v);
222     }
223 
224     save_to_db(
225         tx,
226         ext_id,
227         &StorageChangeOp::Set(JsonValue::Object(current)),
228     )?;
229     Ok(changes)
230 }
231 
232 // A helper which takes a param indicating what keys should be returned and
233 // converts that to a vec of real strings. Also returns "default" values to
234 // be used if no item exists for that key.
get_keys(keys: JsonValue) -> Vec<(String, Option<JsonValue>)>235 fn get_keys(keys: JsonValue) -> Vec<(String, Option<JsonValue>)> {
236     match keys {
237         JsonValue::String(s) => vec![(s, None)],
238         JsonValue::Array(keys) => {
239             // because nothing with json is ever simple, each key may not be
240             // a string. We ignore any which aren't.
241             keys.iter()
242                 .filter_map(|v| v.as_str().map(|s| (s.to_string(), None)))
243                 .collect()
244         }
245         JsonValue::Object(m) => m.into_iter().map(|(k, d)| (k, Some(d))).collect(),
246         _ => vec![],
247     }
248 }
249 
250 /// The implementation of `storage[.sync].get()` - on success this always
251 /// returns a Json object.
get(conn: &Connection, ext_id: &str, keys: JsonValue) -> Result<JsonValue>252 pub fn get(conn: &Connection, ext_id: &str, keys: JsonValue) -> Result<JsonValue> {
253     // key is optional, or string or array of string or object keys
254     let maybe_existing = get_from_db(conn, ext_id)?;
255     let mut existing = match (maybe_existing, keys.is_object()) {
256         (None, true) => return Ok(keys),
257         (None, false) => return Ok(JsonValue::Object(Map::new())),
258         (Some(v), _) => v,
259     };
260     // take the quick path for null, where we just return the entire object.
261     if keys.is_null() {
262         return Ok(JsonValue::Object(existing));
263     }
264     // OK, so we need to build a list of keys to get.
265     let keys_and_defaults = get_keys(keys);
266     let mut result = Map::with_capacity(keys_and_defaults.len());
267     for (key, maybe_default) in keys_and_defaults {
268         if let Some(v) = existing.remove(&key) {
269             result.insert(key, v);
270         } else if let Some(def) = maybe_default {
271             result.insert(key, def);
272         }
273         // else |keys| is a string/array instead of an object with defaults.
274         // Don't include keys without default values.
275     }
276     Ok(JsonValue::Object(result))
277 }
278 
279 /// The implementation of `storage[.sync].remove()`. On success this returns the
280 /// StorageChanges defined by the chrome API - it's assumed the caller will
281 /// arrange to deliver this to observers as defined in that API.
remove(tx: &Transaction<'_>, ext_id: &str, keys: JsonValue) -> Result<StorageChanges>282 pub fn remove(tx: &Transaction<'_>, ext_id: &str, keys: JsonValue) -> Result<StorageChanges> {
283     let mut existing = match get_from_db(tx, ext_id)? {
284         None => return Ok(StorageChanges::new()),
285         Some(v) => v,
286     };
287 
288     // Note: get_keys parses strings, arrays and objects, but remove()
289     // is expected to only be passed a string or array of strings.
290     let keys_and_defs = get_keys(keys);
291 
292     let mut result = StorageChanges::with_capacity(keys_and_defs.len());
293     for (key, _) in keys_and_defs {
294         if let Some(v) = existing.remove(&key) {
295             result.push(StorageValueChange {
296                 key,
297                 old_value: Some(v),
298                 new_value: None,
299             });
300         }
301     }
302     if !result.is_empty() {
303         save_to_db(
304             tx,
305             ext_id,
306             &StorageChangeOp::SetWithoutQuota(JsonValue::Object(existing)),
307         )?;
308     }
309     Ok(result)
310 }
311 
312 /// The implementation of `storage[.sync].clear()`. On success this returns the
313 /// StorageChanges defined by the chrome API - it's assumed the caller will
314 /// arrange to deliver this to observers as defined in that API.
clear(tx: &Transaction<'_>, ext_id: &str) -> Result<StorageChanges>315 pub fn clear(tx: &Transaction<'_>, ext_id: &str) -> Result<StorageChanges> {
316     let existing = match get_from_db(tx, ext_id)? {
317         None => return Ok(StorageChanges::new()),
318         Some(v) => v,
319     };
320     let mut result = StorageChanges::with_capacity(existing.len());
321     for (key, val) in existing.into_iter() {
322         result.push(StorageValueChange {
323             key: key.to_string(),
324             new_value: None,
325             old_value: Some(val),
326         });
327     }
328     remove_from_db(tx, ext_id)?;
329     Ok(result)
330 }
331 
332 /// The implementation of `storage[.sync].getBytesInUse()`.
get_bytes_in_use(conn: &Connection, ext_id: &str, keys: JsonValue) -> Result<usize>333 pub fn get_bytes_in_use(conn: &Connection, ext_id: &str, keys: JsonValue) -> Result<usize> {
334     let maybe_existing = get_from_db(conn, ext_id)?;
335     let existing = match maybe_existing {
336         None => return Ok(0),
337         Some(v) => v,
338     };
339     // Make an array of all the keys we we are going to count.
340     let keys: Vec<&str> = match &keys {
341         JsonValue::Null => existing.keys().map(|v| v.as_str()).collect(),
342         JsonValue::String(name) => vec![name.as_str()],
343         JsonValue::Array(names) => names.iter().filter_map(|v| v.as_str()).collect(),
344         // in the spirit of json-based APIs, silently ignore strange things.
345         _ => return Ok(0),
346     };
347     // We must use the same way of counting as our quota enforcement.
348     let mut size = 0;
349     for key in keys.into_iter() {
350         if let Some(v) = existing.get(key) {
351             size += get_quota_size_of(key, &v);
352         }
353     }
354     Ok(size)
355 }
356 
357 /// Information about the usage of a single extension.
358 #[derive(Debug, Clone, PartialEq)]
359 pub struct UsageInfo {
360     /// The extension id.
361     pub ext_id: String,
362     /// The number of keys the extension uses.
363     pub num_keys: usize,
364     /// The number of bytes used by the extension. This result is somewhat rough
365     /// -- it doesn't bother counting the size of the extension ID, or data in
366     /// the mirror, and favors returning the exact number of bytes used by the
367     /// column (that is, the size of the JSON object) rather than replicating
368     /// the `get_bytes_in_use` return value for all keys.
369     pub num_bytes: usize,
370 }
371 
372 /// Exposes information about per-collection usage for the purpose of telemetry.
373 /// (Doesn't map to an actual `chrome.storage.sync` API).
usage(db: &Connection) -> Result<Vec<UsageInfo>>374 pub fn usage(db: &Connection) -> Result<Vec<UsageInfo>> {
375     type JsonObject = Map<String, JsonValue>;
376     let sql = "
377         SELECT ext_id, data
378         FROM storage_sync_data
379         WHERE data IS NOT NULL
380         -- for tests and determinism
381         ORDER BY ext_id
382     ";
383     db.query_rows_into(sql, &[], |row| {
384         let ext_id: String = row.get("ext_id")?;
385         let data: String = row.get("data")?;
386         let num_bytes = data.len();
387         let num_keys = serde_json::from_str::<JsonObject>(&data)?.len();
388         Ok(UsageInfo {
389             ext_id,
390             num_bytes,
391             num_keys,
392         })
393     })
394 }
395 
396 #[cfg(test)]
397 mod tests {
398     use super::*;
399     use crate::db::test::new_mem_db;
400     use serde_json::json;
401 
402     #[test]
test_serialize_storage_changes() -> Result<()>403     fn test_serialize_storage_changes() -> Result<()> {
404         let c = StorageChanges {
405             changes: vec![StorageValueChange {
406                 key: "key".to_string(),
407                 old_value: Some(json!("old")),
408                 new_value: None,
409             }],
410         };
411         assert_eq!(serde_json::to_string(&c)?, r#"{"key":{"oldValue":"old"}}"#);
412         let c = StorageChanges {
413             changes: vec![StorageValueChange {
414                 key: "key".to_string(),
415                 old_value: None,
416                 new_value: Some(json!({"foo": "bar"})),
417             }],
418         };
419         assert_eq!(
420             serde_json::to_string(&c)?,
421             r#"{"key":{"newValue":{"foo":"bar"}}}"#
422         );
423         Ok(())
424     }
425 
make_changes(changes: &[(&str, Option<JsonValue>, Option<JsonValue>)]) -> StorageChanges426     fn make_changes(changes: &[(&str, Option<JsonValue>, Option<JsonValue>)]) -> StorageChanges {
427         let mut r = StorageChanges::with_capacity(changes.len());
428         for (name, old_value, new_value) in changes {
429             r.push(StorageValueChange {
430                 key: (*name).to_string(),
431                 old_value: old_value.clone(),
432                 new_value: new_value.clone(),
433             });
434         }
435         r
436     }
437 
438     #[test]
test_simple() -> Result<()>439     fn test_simple() -> Result<()> {
440         let ext_id = "x";
441         let mut db = new_mem_db();
442         let tx = db.transaction()?;
443 
444         // an empty store.
445         for q in vec![JsonValue::Null, json!("foo"), json!(["foo"])].into_iter() {
446             assert_eq!(get(&tx, &ext_id, q)?, json!({}));
447         }
448 
449         // Default values in an empty store.
450         for q in vec![json!({ "foo": null }), json!({"foo": "default"})].into_iter() {
451             assert_eq!(get(&tx, &ext_id, q.clone())?, q.clone());
452         }
453 
454         // Single item in the store.
455         set(&tx, &ext_id, json!({"foo": "bar" }))?;
456         for q in vec![
457             JsonValue::Null,
458             json!("foo"),
459             json!(["foo"]),
460             json!({ "foo": null }),
461             json!({"foo": "default"}),
462         ]
463         .into_iter()
464         {
465             assert_eq!(get(&tx, &ext_id, q)?, json!({"foo": "bar" }));
466         }
467 
468         // Default values in a non-empty store.
469         for q in vec![
470             json!({ "non_existing_key": null }),
471             json!({"non_existing_key": 0}),
472             json!({"non_existing_key": false}),
473             json!({"non_existing_key": "default"}),
474             json!({"non_existing_key": ["array"]}),
475             json!({"non_existing_key": {"objectkey": "value"}}),
476         ]
477         .into_iter()
478         {
479             assert_eq!(get(&tx, &ext_id, q.clone())?, q.clone());
480         }
481 
482         // more complex stuff, including changes checking.
483         assert_eq!(
484             set(&tx, &ext_id, json!({"foo": "new", "other": "also new" }))?,
485             make_changes(&[
486                 ("foo", Some(json!("bar")), Some(json!("new"))),
487                 ("other", None, Some(json!("also new")))
488             ])
489         );
490         assert_eq!(
491             get(&tx, &ext_id, JsonValue::Null)?,
492             json!({"foo": "new", "other": "also new"})
493         );
494         assert_eq!(get(&tx, &ext_id, json!("foo"))?, json!({"foo": "new"}));
495         assert_eq!(
496             get(&tx, &ext_id, json!(["foo", "other"]))?,
497             json!({"foo": "new", "other": "also new"})
498         );
499         assert_eq!(
500             get(&tx, &ext_id, json!({"foo": null, "default": "yo"}))?,
501             json!({"foo": "new", "default": "yo"})
502         );
503 
504         assert_eq!(
505             remove(&tx, &ext_id, json!("foo"))?,
506             make_changes(&[("foo", Some(json!("new")), None)]),
507         );
508 
509         assert_eq!(
510             set(&tx, &ext_id, json!({"foo": {"sub-object": "sub-value"}}))?,
511             make_changes(&[("foo", None, Some(json!({"sub-object": "sub-value"}))),])
512         );
513 
514         // XXX - other variants.
515 
516         assert_eq!(
517             clear(&tx, &ext_id)?,
518             make_changes(&[
519                 ("foo", Some(json!({"sub-object": "sub-value"})), None),
520                 ("other", Some(json!("also new")), None),
521             ]),
522         );
523         assert_eq!(get(&tx, &ext_id, JsonValue::Null)?, json!({}));
524 
525         Ok(())
526     }
527 
528     #[test]
test_check_get_impl() -> Result<()>529     fn test_check_get_impl() -> Result<()> {
530         // This is a port of checkGetImpl in test_ext_storage.js in Desktop.
531         let ext_id = "x";
532         let mut db = new_mem_db();
533         let tx = db.transaction()?;
534 
535         let prop = "test-prop";
536         let value = "test-value";
537 
538         set(&tx, ext_id, json!({ prop: value }))?;
539 
540         // this is the checkGetImpl part!
541         let mut data = get(&tx, &ext_id, json!(null))?;
542         assert_eq!(value, json!(data[prop]), "null getter worked for {}", prop);
543 
544         data = get(&tx, &ext_id, json!(prop))?;
545         assert_eq!(
546             value,
547             json!(data[prop]),
548             "string getter worked for {}",
549             prop
550         );
551         assert_eq!(
552             data.as_object().unwrap().len(),
553             1,
554             "string getter should return an object with a single property"
555         );
556 
557         data = get(&tx, &ext_id, json!([prop]))?;
558         assert_eq!(value, json!(data[prop]), "array getter worked for {}", prop);
559         assert_eq!(
560             data.as_object().unwrap().len(),
561             1,
562             "array getter with a single key should return an object with a single property"
563         );
564 
565         // checkGetImpl() uses `{ [prop]: undefined }` - but json!() can't do that :(
566         // Hopefully it's just testing a simple object, so we use `{ prop: null }`
567         data = get(&tx, &ext_id, json!({ prop: null }))?;
568         assert_eq!(
569             value,
570             json!(data[prop]),
571             "object getter worked for {}",
572             prop
573         );
574         assert_eq!(
575             data.as_object().unwrap().len(),
576             1,
577             "object getter with a single key should return an object with a single property"
578         );
579 
580         Ok(())
581     }
582 
583     #[test]
test_bug_1621162() -> Result<()>584     fn test_bug_1621162() -> Result<()> {
585         // apparently Firefox, unlike Chrome, will not optimize the changes.
586         // See bug 1621162 for more!
587         let mut db = new_mem_db();
588         let tx = db.transaction()?;
589         let ext_id = "xyz";
590 
591         set(&tx, &ext_id, json!({"foo": "bar" }))?;
592 
593         assert_eq!(
594             set(&tx, &ext_id, json!({"foo": "bar" }))?,
595             make_changes(&[("foo", Some(json!("bar")), Some(json!("bar")))]),
596         );
597         Ok(())
598     }
599 
600     #[test]
test_quota_maxitems() -> Result<()>601     fn test_quota_maxitems() -> Result<()> {
602         let mut db = new_mem_db();
603         let tx = db.transaction()?;
604         let ext_id = "xyz";
605         for i in 1..SYNC_MAX_ITEMS + 1 {
606             set(
607                 &tx,
608                 &ext_id,
609                 json!({ format!("key-{}", i): format!("value-{}", i) }),
610             )?;
611         }
612         let e = set(&tx, &ext_id, json!({"another": "another"})).unwrap_err();
613         match e.kind() {
614             ErrorKind::QuotaError(QuotaReason::MaxItems) => {}
615             _ => panic!("unexpected error type"),
616         };
617         Ok(())
618     }
619 
620     #[test]
test_quota_bytesperitem() -> Result<()>621     fn test_quota_bytesperitem() -> Result<()> {
622         let mut db = new_mem_db();
623         let tx = db.transaction()?;
624         let ext_id = "xyz";
625         // A string 5 bytes less than the max. This should be counted as being
626         // 3 bytes less than the max as the quotes are counted. Plus the length
627         // of the key (no quotes) means we should come in 2 bytes under.
628         let val = "x".repeat(SYNC_QUOTA_BYTES_PER_ITEM - 5);
629 
630         // Key length doesn't push it over.
631         set(&tx, &ext_id, json!({ "x": val }))?;
632         assert_eq!(
633             get_bytes_in_use(&tx, &ext_id, json!("x"))?,
634             SYNC_QUOTA_BYTES_PER_ITEM - 2
635         );
636 
637         // Key length does push it over.
638         let e = set(&tx, &ext_id, json!({ "xxxx": val })).unwrap_err();
639         match e.kind() {
640             ErrorKind::QuotaError(QuotaReason::ItemBytes) => {}
641             _ => panic!("unexpected error type"),
642         };
643         Ok(())
644     }
645 
646     #[test]
test_quota_bytes() -> Result<()>647     fn test_quota_bytes() -> Result<()> {
648         let mut db = new_mem_db();
649         let tx = db.transaction()?;
650         let ext_id = "xyz";
651         let val = "x".repeat(SYNC_QUOTA_BYTES + 1);
652 
653         // Init an over quota db with a single key.
654         save_to_db(
655             &tx,
656             ext_id,
657             &StorageChangeOp::SetWithoutQuota(json!({ "x": val })),
658         )?;
659 
660         // Adding more data fails.
661         let e = set(&tx, &ext_id, json!({ "y": "newvalue" })).unwrap_err();
662         match e.kind() {
663             ErrorKind::QuotaError(QuotaReason::TotalBytes) => {}
664             _ => panic!("unexpected error type"),
665         };
666 
667         // Remove data does not fails.
668         remove(&tx, &ext_id, json!["x"])?;
669 
670         // Restore the over quota data.
671         save_to_db(
672             &tx,
673             ext_id,
674             &StorageChangeOp::SetWithoutQuota(json!({ "y": val })),
675         )?;
676 
677         // Overwrite with less data does not fail.
678         set(&tx, &ext_id, json!({ "y": "lessdata" }))?;
679 
680         Ok(())
681     }
682 
683     #[test]
test_get_bytes_in_use() -> Result<()>684     fn test_get_bytes_in_use() -> Result<()> {
685         let mut db = new_mem_db();
686         let tx = db.transaction()?;
687         let ext_id = "xyz";
688 
689         assert_eq!(get_bytes_in_use(&tx, &ext_id, json!(null))?, 0);
690 
691         set(&tx, &ext_id, json!({ "a": "a" }))?; // should be 4
692         set(&tx, &ext_id, json!({ "b": "bb" }))?; // should be 5
693         set(&tx, &ext_id, json!({ "c": "ccc" }))?; // should be 6
694         set(&tx, &ext_id, json!({ "n": 999_999 }))?; // should be 7
695 
696         assert_eq!(get_bytes_in_use(&tx, &ext_id, json!("x"))?, 0);
697         assert_eq!(get_bytes_in_use(&tx, &ext_id, json!("a"))?, 4);
698         assert_eq!(get_bytes_in_use(&tx, &ext_id, json!("b"))?, 5);
699         assert_eq!(get_bytes_in_use(&tx, &ext_id, json!("c"))?, 6);
700         assert_eq!(get_bytes_in_use(&tx, &ext_id, json!("n"))?, 7);
701 
702         assert_eq!(get_bytes_in_use(&tx, &ext_id, json!(["a"]))?, 4);
703         assert_eq!(get_bytes_in_use(&tx, &ext_id, json!(["a", "x"]))?, 4);
704         assert_eq!(get_bytes_in_use(&tx, &ext_id, json!(["a", "b"]))?, 9);
705         assert_eq!(get_bytes_in_use(&tx, &ext_id, json!(["a", "c"]))?, 10);
706 
707         assert_eq!(
708             get_bytes_in_use(&tx, &ext_id, json!(["a", "b", "c", "n"]))?,
709             22
710         );
711         assert_eq!(get_bytes_in_use(&tx, &ext_id, json!(null))?, 22);
712         Ok(())
713     }
714 
715     #[test]
test_usage()716     fn test_usage() {
717         let mut db = new_mem_db();
718         let tx = db.transaction().unwrap();
719         // '{"a":"a","b":"bb","c":"ccc","n":999999}': 39 bytes
720         set(&tx, "xyz", json!({ "a": "a" })).unwrap();
721         set(&tx, "xyz", json!({ "b": "bb" })).unwrap();
722         set(&tx, "xyz", json!({ "c": "ccc" })).unwrap();
723         set(&tx, "xyz", json!({ "n": 999_999 })).unwrap();
724 
725         // '{"a":"a"}': 9 bytes
726         set(&tx, "abc", json!({ "a": "a" })).unwrap();
727 
728         tx.commit().unwrap();
729 
730         let usage = usage(&db).unwrap();
731         let expect = [
732             UsageInfo {
733                 ext_id: "abc".to_string(),
734                 num_keys: 1,
735                 num_bytes: 9,
736             },
737             UsageInfo {
738                 ext_id: "xyz".to_string(),
739                 num_keys: 4,
740                 num_bytes: 39,
741             },
742         ];
743         assert_eq!(&usage, &expect);
744     }
745 }
746