use std::{
iter::{once, Once},
slice::Iter,
};
use blake3::Hasher;
use serde_json::{Number, Value};
use crate::encryption::{EncryptionError, IndexTerm};
pub struct JsonContainmentIndexer {
key: [u8; 32],
prefix: Vec<u8>,
}
impl JsonContainmentIndexer {
pub fn new(key: [u8; 32], prefix: String) -> Self {
Self {
key,
prefix: prefix.as_bytes().into(),
}
}
pub fn encrypt(&self, json: Value) -> Result<IndexTerm, EncryptionError> {
let mut hashes: Vec<blake3::Hash> = Vec::new();
for PathValue(path, leaf) in PathHashTargets::new(&json) {
let mut hasher = Hasher::new_keyed(&self.key);
hasher.update(&self.prefix);
for segment in path[1..].iter() {
segment.update_hash(&mut hasher);
hashes.push(hasher.finalize());
}
leaf.update_hash(&mut hasher);
hashes.push(hasher.finalize());
}
Ok(IndexTerm::BinaryVec(
hashes
.into_iter()
.map(|hash| Vec::from(hash.as_bytes()))
.collect(),
))
}
}
#[derive(Debug, PartialEq, Eq, Hash)]
enum HashTarget<'a> {
Bool(bool),
Number(&'a Number),
String(&'a str),
Array,
Object,
}
impl<'a> HashTarget<'a> {
fn update_hash(&self, hasher: &mut Hasher) {
match self {
HashTarget::Bool(x) => hasher.update(format!("Bool-{x}").as_bytes()),
HashTarget::Number(x) => hasher.update(format!("Number-{x}").as_bytes()),
HashTarget::String(x) => hasher.update(format!("String-{x}").as_bytes()),
HashTarget::Array => hasher.update("Array".as_bytes()),
HashTarget::Object => hasher.update("Object".as_bytes()),
};
}
}
#[derive(Debug, PartialEq, Eq, Hash)]
struct PathValue<'a>(Vec<PathSegment<'a>>, HashTarget<'a>);
enum JsonIter<'a> {
Bool(PathSegment<'a>, Once<&'a Value>),
Number(PathSegment<'a>, Once<&'a Value>),
String(PathSegment<'a>, Once<&'a Value>),
Array(PathSegment<'a>, Iter<'a, Value>),
Object(PathSegment<'a>, serde_json::map::Iter<'a>),
}
impl<'a> JsonIter<'a> {
fn idx(&self) -> PathSegment<'a> {
match self {
JsonIter::Bool(idx, _) => idx.clone(),
JsonIter::Number(idx, _) => idx.clone(),
JsonIter::String(idx, _) => idx.clone(),
JsonIter::Array(idx, _) => idx.clone(),
JsonIter::Object(idx, _) => idx.clone(),
}
}
}
struct PathHashTargets<'a> {
stack: Vec<JsonIter<'a>>,
}
impl<'a> PathHashTargets<'a> {
fn new(json: &'a Value) -> Self {
Self {
stack: match json {
Value::Null => vec![],
Value::Bool(_) => vec![JsonIter::Bool(PathSegment::Root, once(json))],
Value::Number(_) => vec![JsonIter::Number(PathSegment::Root, once(json))],
Value::String(_) => vec![JsonIter::String(PathSegment::Root, once(json))],
Value::Array(array) => {
vec![JsonIter::Array(PathSegment::Root, array.iter())]
}
Value::Object(object) => {
vec![JsonIter::Object(PathSegment::Root, object.into_iter())]
}
},
}
}
fn path(&self, segment: PathSegment<'a>) -> Vec<PathSegment<'a>> {
Vec::from_iter(
self.stack
.iter()
.map(|node| node.idx())
.chain(once(segment)),
)
}
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
enum PathSegment<'a> {
Root,
ArrayItem,
ObjectItem(&'a str),
}
impl<'a> PathSegment<'a> {
fn update_hash(&self, hasher: &mut Hasher) {
match self {
PathSegment::Root => hasher.update("Root".as_bytes()),
PathSegment::ArrayItem => hasher.update("ArrayItem".as_bytes()),
PathSegment::ObjectItem(key) => hasher.update(format!("ObjectItem-{}", key).as_bytes()),
};
}
}
impl<'a> Iterator for JsonIter<'a> {
type Item = (PathSegment<'a>, &'a Value);
fn next(&mut self) -> Option<Self::Item> {
match self {
JsonIter::Number(path_segment, iter) => iter.next().map(|v| (path_segment.clone(), v)),
JsonIter::String(path_segment, iter) => iter.next().map(|v| (path_segment.clone(), v)),
JsonIter::Bool(path_segment, iter) => iter.next().map(|v| (path_segment.clone(), v)),
JsonIter::Array(_, iter) => iter.next().map(|v| (PathSegment::ArrayItem, v)),
JsonIter::Object(_, iter) => iter
.next()
.map(|(key, v)| (PathSegment::ObjectItem(key), v)),
}
}
}
impl<'a> Iterator for PathHashTargets<'a> {
type Item = PathValue<'a>;
fn next(&mut self) -> Option<Self::Item> {
loop {
match self.stack.last_mut() {
Some(value_iter) => match value_iter.next() {
Some((segment, value)) => match value {
Value::Null => {}
Value::Bool(x) => {
return Some(PathValue(self.path(segment), HashTarget::Bool(*x)))
}
Value::Number(x) => {
return Some(PathValue(self.path(segment), HashTarget::Number(x)))
}
Value::String(x) => {
return Some(PathValue(self.path(segment), HashTarget::String(x)))
}
Value::Array(array) => {
let arr_value =
PathValue(self.path(segment.clone()), HashTarget::Array);
self.stack.push(JsonIter::Array(segment, array.iter()));
return Some(arr_value);
}
Value::Object(map) => {
let object_value =
PathValue(self.path(segment.clone()), HashTarget::Object);
self.stack.push(JsonIter::Object(segment, map.into_iter()));
return Some(object_value);
}
},
None => {
self.stack.pop();
}
},
None => return None,
}
}
}
}
#[cfg(test)]
mod tests {
use std::collections::HashSet;
use super::*;
use serde_json::Number;
#[test]
fn test_flattening() {
let json = serde_json::json!({
"name": "John",
"age": 30,
"cars": [
"Ford",
"BMW",
"Fiat",
{ "Tesla": "Model S" }
]
});
let flattened: HashSet<PathValue> = HashSet::from_iter(PathHashTargets::new(&json));
assert!(flattened.contains(&PathValue(
vec![PathSegment::Root, PathSegment::ObjectItem("name")],
HashTarget::String("John")
)));
assert!(flattened.contains(&PathValue(
vec![PathSegment::Root, PathSegment::ObjectItem("age")],
HashTarget::Number(&Number::from(30))
)));
assert!(flattened.contains(&PathValue(
vec![
PathSegment::Root,
PathSegment::ObjectItem("cars"),
PathSegment::ArrayItem,
],
HashTarget::String("Ford")
)));
assert!(flattened.contains(&PathValue(
vec![
PathSegment::Root,
PathSegment::ObjectItem("cars"),
PathSegment::ArrayItem,
],
HashTarget::String("BMW")
)));
assert!(flattened.contains(&PathValue(
vec![
PathSegment::Root,
PathSegment::ObjectItem("cars"),
PathSegment::ArrayItem,
],
HashTarget::String("Fiat")
)));
assert!(flattened.contains(&PathValue(
vec![
PathSegment::Root,
PathSegment::ObjectItem("cars"),
PathSegment::ArrayItem,
PathSegment::ObjectItem("Tesla"),
],
HashTarget::String("Model S")
)));
}
#[test]
fn test_encrypt() {
let indexer = JsonContainmentIndexer::new([0; 32], "cars".into());
let json = serde_json::json!({
"name": "John",
"null": null,
"age": 30,
"cars": [
"Ford",
"BMW",
null,
"Fiat"
]
});
if let IndexTerm::BinaryVec(terms) = indexer.encrypt(json).unwrap() {
assert_eq!(terms.len(), 15);
} else {
panic!();
}
}
#[test]
fn find_by_leaf_node() {
let json = serde_json::json!({
"name": "John",
"age": 30,
"cars": [
"Ford",
"BMW",
"Fiat",
{ "Tesla": "Model S" }
]
});
let indexer = JsonContainmentIndexer::new([0; 32], "cars".into());
if let IndexTerm::BinaryVec(terms) = indexer.encrypt(json).unwrap() {
let Ok(IndexTerm::BinaryVec(search_terms)) = indexer.encrypt(serde_json::json!({
"name": "John"
})) else {
panic!();
};
assert!(search_terms
.iter()
.all(|term| terms.iter().any(|candidate| term == candidate)));
let Ok(IndexTerm::BinaryVec(search_terms)) = indexer.encrypt(serde_json::json!({
"cars": [
{ "Tesla": "Model S" }
]
})) else {
panic!();
};
assert!(search_terms
.iter()
.all(|term| terms.iter().any(|candidate| term == candidate)));
} else {
panic!();
}
}
#[test]
fn find_by_non_existing_leaf_node() {
let json = serde_json::json!({
"name": "John",
"age": 30,
"cars": [
"Ford",
"BMW",
"Fiat",
{ "Tesla": "Model S" }
]
});
let indexer = JsonContainmentIndexer::new([0; 32], "cars".into());
if let IndexTerm::BinaryVec(terms) = indexer.encrypt(json).unwrap() {
let Ok(IndexTerm::BinaryVec(search_terms)) =
indexer.encrypt(serde_json::json!(["Ford"]))
else {
panic!();
};
assert!(!search_terms
.iter()
.all(|term| terms.iter().any(|candidate| term == candidate)));
} else {
panic!();
}
}
#[test]
fn find_by_non_leaf_node() {
let json = serde_json::json!({
"name": "John",
"age": 30,
"cars": [
"Ford",
"BMW",
"Fiat",
{ "Tesla": "Model S" }
]
});
let indexer = JsonContainmentIndexer::new([0; 32], "cars".into());
if let IndexTerm::BinaryVec(terms) = indexer.encrypt(json).unwrap() {
let Ok(IndexTerm::BinaryVec(search_terms)) = indexer.encrypt(serde_json::json!({
"cars": []
})) else {
panic!();
};
assert_eq!(search_terms.len(), 2);
assert!(search_terms
.iter()
.all(|term| terms.iter().any(|candidate| term == candidate)));
} else {
panic!();
}
}
#[test]
fn find_by_non_existing_non_leaf_node() {
let json = serde_json::json!({
"name": "John",
"age": 30,
"cars": [
"Ford",
"BMW",
"Fiat",
{ "Tesla": "Model S" }
]
});
let indexer = JsonContainmentIndexer::new([0; 32], "cars".into());
if let IndexTerm::BinaryVec(terms) = indexer.encrypt(json).unwrap() {
let Ok(IndexTerm::BinaryVec(search_terms)) = indexer.encrypt(serde_json::json!({
"cars": {}
})) else {
panic!();
};
assert_eq!(search_terms.len(), 2);
assert!(!search_terms
.iter()
.all(|term| terms.iter().any(|candidate| term == candidate)));
} else {
panic!();
}
}
}