2023-06-26 08:52:37 +02:00
|
|
|
use atuin_common::record::DecryptedData;
|
2023-06-18 13:33:55 +02:00
|
|
|
use eyre::{bail, ensure, eyre, Result};
|
2023-06-14 22:18:24 +02:00
|
|
|
|
2023-06-26 08:52:37 +02:00
|
|
|
use crate::record::encryption::PASETO_V4;
|
2023-06-14 22:18:24 +02:00
|
|
|
use crate::record::store::Store;
|
|
|
|
use crate::settings::Settings;
|
|
|
|
|
|
|
|
const KV_VERSION: &str = "v0";
|
|
|
|
const KV_TAG: &str = "kv";
|
|
|
|
|
2023-06-18 13:33:55 +02:00
|
|
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
2023-06-14 22:18:24 +02:00
|
|
|
pub struct KvRecord {
|
2023-06-15 09:17:30 +02:00
|
|
|
pub namespace: String,
|
2023-06-14 22:18:24 +02:00
|
|
|
pub key: String,
|
|
|
|
pub value: String,
|
|
|
|
}
|
|
|
|
|
|
|
|
impl KvRecord {
|
2023-06-26 08:52:37 +02:00
|
|
|
pub fn serialize(&self) -> Result<DecryptedData> {
|
2023-06-18 13:33:55 +02:00
|
|
|
use rmp::encode;
|
2023-06-14 22:18:24 +02:00
|
|
|
|
2023-06-18 13:33:55 +02:00
|
|
|
let mut output = vec![];
|
|
|
|
|
|
|
|
// INFO: ensure this is updated when adding new fields
|
|
|
|
encode::write_array_len(&mut output, 3)?;
|
|
|
|
|
|
|
|
encode::write_str(&mut output, &self.namespace)?;
|
|
|
|
encode::write_str(&mut output, &self.key)?;
|
|
|
|
encode::write_str(&mut output, &self.value)?;
|
|
|
|
|
2023-06-26 08:52:37 +02:00
|
|
|
Ok(DecryptedData(output))
|
2023-06-18 13:33:55 +02:00
|
|
|
}
|
|
|
|
|
2023-06-26 08:52:37 +02:00
|
|
|
pub fn deserialize(data: &DecryptedData, version: &str) -> Result<Self> {
|
2023-06-18 13:33:55 +02:00
|
|
|
use rmp::decode;
|
|
|
|
|
|
|
|
fn error_report<E: std::fmt::Debug>(err: E) -> eyre::Report {
|
|
|
|
eyre!("{err:?}")
|
|
|
|
}
|
|
|
|
|
|
|
|
match version {
|
|
|
|
KV_VERSION => {
|
2023-06-26 08:52:37 +02:00
|
|
|
let mut bytes = decode::Bytes::new(&data.0);
|
2023-06-18 13:33:55 +02:00
|
|
|
|
|
|
|
let nfields = decode::read_array_len(&mut bytes).map_err(error_report)?;
|
|
|
|
ensure!(nfields == 3, "too many entries in v0 kv record");
|
|
|
|
|
|
|
|
let bytes = bytes.remaining_slice();
|
|
|
|
|
|
|
|
let (namespace, bytes) =
|
|
|
|
decode::read_str_from_slice(bytes).map_err(error_report)?;
|
|
|
|
let (key, bytes) = decode::read_str_from_slice(bytes).map_err(error_report)?;
|
|
|
|
let (value, bytes) = decode::read_str_from_slice(bytes).map_err(error_report)?;
|
|
|
|
|
|
|
|
if !bytes.is_empty() {
|
|
|
|
bail!("trailing bytes in encoded kvrecord. malformed")
|
|
|
|
}
|
|
|
|
|
|
|
|
Ok(KvRecord {
|
|
|
|
namespace: namespace.to_owned(),
|
|
|
|
key: key.to_owned(),
|
|
|
|
value: value.to_owned(),
|
|
|
|
})
|
|
|
|
}
|
|
|
|
_ => {
|
|
|
|
bail!("unknown version {version:?}")
|
|
|
|
}
|
|
|
|
}
|
2023-06-14 22:18:24 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
pub struct KvStore;
|
|
|
|
|
|
|
|
impl Default for KvStore {
|
|
|
|
fn default() -> Self {
|
|
|
|
Self::new()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl KvStore {
|
|
|
|
// will want to init the actual kv store when that is done
|
|
|
|
pub fn new() -> KvStore {
|
|
|
|
KvStore {}
|
|
|
|
}
|
|
|
|
|
|
|
|
pub async fn set(
|
|
|
|
&self,
|
|
|
|
store: &mut (impl Store + Send + Sync),
|
2023-06-26 08:52:37 +02:00
|
|
|
encryption_key: &[u8; 32],
|
2023-06-15 09:17:30 +02:00
|
|
|
namespace: &str,
|
2023-06-14 22:18:24 +02:00
|
|
|
key: &str,
|
|
|
|
value: &str,
|
|
|
|
) -> Result<()> {
|
|
|
|
let host_id = Settings::host_id().expect("failed to get host_id");
|
|
|
|
|
|
|
|
let record = KvRecord {
|
2023-06-15 09:17:30 +02:00
|
|
|
namespace: namespace.to_string(),
|
2023-06-14 22:18:24 +02:00
|
|
|
key: key.to_string(),
|
|
|
|
value: value.to_string(),
|
|
|
|
};
|
|
|
|
|
|
|
|
let bytes = record.serialize()?;
|
|
|
|
|
2023-07-14 21:44:08 +02:00
|
|
|
let parent = store.tail(host_id, KV_TAG).await?.map(|entry| entry.id);
|
2023-06-14 22:18:24 +02:00
|
|
|
|
|
|
|
let record = atuin_common::record::Record::builder()
|
|
|
|
.host(host_id)
|
|
|
|
.version(KV_VERSION.to_string())
|
|
|
|
.tag(KV_TAG.to_string())
|
|
|
|
.parent(parent)
|
|
|
|
.data(bytes)
|
|
|
|
.build();
|
|
|
|
|
2023-06-26 08:52:37 +02:00
|
|
|
store
|
|
|
|
.push(&record.encrypt::<PASETO_V4>(encryption_key))
|
|
|
|
.await?;
|
2023-06-14 22:18:24 +02:00
|
|
|
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
|
|
|
// TODO: setup an actual kv store, rebuild func, and do not pass the main store in here as
|
|
|
|
// well.
|
2023-06-15 09:17:30 +02:00
|
|
|
pub async fn get(
|
|
|
|
&self,
|
|
|
|
store: &impl Store,
|
2023-06-26 08:52:37 +02:00
|
|
|
encryption_key: &[u8; 32],
|
2023-06-15 09:17:30 +02:00
|
|
|
namespace: &str,
|
|
|
|
key: &str,
|
|
|
|
) -> Result<Option<KvRecord>> {
|
2023-06-14 22:18:24 +02:00
|
|
|
// Currently, this is O(n). When we have an actual KV store, it can be better
|
|
|
|
// Just a poc for now!
|
|
|
|
|
|
|
|
// iterate records to find the value we want
|
|
|
|
// start at the end, so we get the most recent version
|
2023-07-14 21:44:08 +02:00
|
|
|
let tails = store.tag_tails(KV_TAG).await?;
|
|
|
|
|
|
|
|
if tails.is_empty() {
|
2023-06-14 22:18:24 +02:00
|
|
|
return Ok(None);
|
2023-07-14 21:44:08 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
// first, decide on a record.
|
|
|
|
// try getting the newest first
|
|
|
|
// we always need a way of deciding the "winner" of a write
|
|
|
|
// TODO(ellie): something better than last-write-wins, what if two write at the same time?
|
|
|
|
let mut record = tails.iter().max_by_key(|r| r.timestamp).unwrap().clone();
|
2023-06-14 22:18:24 +02:00
|
|
|
|
2023-06-18 13:33:55 +02:00
|
|
|
loop {
|
2023-06-26 08:52:37 +02:00
|
|
|
let decrypted = match record.version.as_str() {
|
|
|
|
KV_VERSION => record.decrypt::<PASETO_V4>(encryption_key)?,
|
|
|
|
version => bail!("unknown version {version:?}"),
|
|
|
|
};
|
|
|
|
|
|
|
|
let kv = KvRecord::deserialize(&decrypted.data, &decrypted.version)?;
|
2023-06-15 09:17:30 +02:00
|
|
|
if kv.key == key && kv.namespace == namespace {
|
2023-06-14 22:18:24 +02:00
|
|
|
return Ok(Some(kv));
|
|
|
|
}
|
2023-06-18 13:33:55 +02:00
|
|
|
|
2023-06-26 08:52:37 +02:00
|
|
|
if let Some(parent) = decrypted.parent {
|
2023-07-14 21:44:08 +02:00
|
|
|
record = store.get(parent).await?;
|
2023-06-18 13:33:55 +02:00
|
|
|
} else {
|
|
|
|
break;
|
|
|
|
}
|
2023-06-14 22:18:24 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
// if we get here, then... we didn't find the record with that key :(
|
|
|
|
Ok(None)
|
|
|
|
}
|
|
|
|
}
|
2023-06-18 13:33:55 +02:00
|
|
|
|
|
|
|
#[cfg(test)]
|
|
|
|
mod tests {
|
|
|
|
use super::{KvRecord, KV_VERSION};
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn encode_decode() {
|
|
|
|
let kv = KvRecord {
|
|
|
|
namespace: "foo".to_owned(),
|
|
|
|
key: "bar".to_owned(),
|
|
|
|
value: "baz".to_owned(),
|
|
|
|
};
|
|
|
|
let snapshot = [
|
|
|
|
0x93, 0xa3, b'f', b'o', b'o', 0xa3, b'b', b'a', b'r', 0xa3, b'b', b'a', b'z',
|
|
|
|
];
|
|
|
|
|
|
|
|
let encoded = kv.serialize().unwrap();
|
|
|
|
let decoded = KvRecord::deserialize(&encoded, KV_VERSION).unwrap();
|
|
|
|
|
2023-06-26 08:52:37 +02:00
|
|
|
assert_eq!(encoded.0, &snapshot);
|
2023-06-18 13:33:55 +02:00
|
|
|
assert_eq!(decoded, kv);
|
|
|
|
}
|
|
|
|
}
|