Browse Source

First practical tests with commits.

tags/v0.1.0
Thomas Kerber 2 years ago
parent
commit
844ed268ed
4 changed files with 98 additions and 72 deletions
  1. 1
    1
      Cargo.toml
  2. 33
    35
      src/format.rs
  3. 45
    34
      src/keyring.rs
  4. 19
    2
      src/main.rs

+ 1
- 1
Cargo.toml View File

@@ -12,5 +12,5 @@ gcrypt = "*"
tempdir = "*"
rand = "*"
hex = "*"
log = "*"
log = {version="*", features=["release_max_level_debug"]}
env_logger = "*"

+ 33
- 35
src/format.rs View File

@@ -31,7 +31,7 @@ impl Vault {
pub fn create(file: PathBuf, passphrase: Vec<u8>) -> Result<Self> {
let dir = TempDir::new("goblin")?;
info!(
"creating new vault '{}', working in '{}'",
"creating new vault {:?}, working in {:?}",
file.display(),
dir.path().display()
);
@@ -54,7 +54,7 @@ impl Vault {
pub fn open(file: PathBuf, passphrase: Vec<u8>) -> io::Result<Self> {
let dir = TempDir::new("goblin")?;
info!(
"opening vault '{}', working in '{}'",
"opening vault {:?}, working in {:?}",
file.display(),
dir.path().display()
);
@@ -94,29 +94,24 @@ impl Vault {
tb.append_dir_all("", self.extracted_to.path())?;
}
fs::rename(&ftmpname, &self.file)?;
info!("vault '{}' closed", self.file.display());
info!("vault {:?} closed", self.file.display());
Ok(())
}

pub fn current_item(&self) -> Result<Option<HistoryItem>> {
pub fn current_commit(&self) -> Result<Option<Commit>> {
if self.metadata.current == Hash::default() {
Ok(None)
} else {
HistoryItem::from_hash(&self, self.metadata.current).map(|i| Some(i))
Commit::from_hash(&self, self.metadata.current).map(|i| Some(i))
}
}

fn unlock(&mut self, passphrase: Vec<u8>) -> Result<()> {
let derived_key = Key::derive(&passphrase[..], &self.metadata.salt)?;
let keyfile = File::open(self.path(format!(
"sha3_256:{}.key",
Hash::new(derived_key.as_ref()).to_hex()
)))?;
let path = format!("{}.key", Hash::new(derived_key.as_ref()).to_hex());
let keyfile = File::open(self.path(&path))?;
self.key = Key::from_keyfile(keyfile, &derived_key)?;
debug!(
"unlocked with key sha3_256:{}.key",
Hash::new(derived_key.as_ref()).to_hex()
);
debug!("unlocked with key {:?}", path);
Ok(())
}

@@ -126,15 +121,10 @@ impl Vault {

fn add_passphrase(&self, passphrase: Vec<u8>) -> Result<()> {
let derived_key = Key::derive(&passphrase[..], &self.metadata.salt)?;
let keyfile = create_umask().open(self.path(format!(
"sha3_256:{}.key",
Hash::new(derived_key.as_ref()).to_hex()
)))?;
let path = format!("{}.key", Hash::new(derived_key.as_ref()).to_hex());
let keyfile = create_umask().open(self.path(&path))?;
encrypt(self.key.as_ref().to_owned(), &derived_key, keyfile)?;
debug!(
"added new key sha3_256:{}.key",
Hash::new(derived_key.as_ref()).to_hex()
);
debug!("added new key {:?}", path);
Ok(())
}

@@ -173,36 +163,42 @@ pub enum Delta {
}

#[derive(Serialize, Deserialize)]
pub struct HistoryItem {
pub struct Commit {
pub previous: Vec<Hash>,
pub deltas: Vec<Delta>,
#[serde(skip)]
pub hash: Hash,
}

impl HistoryItem {
impl Commit {
pub fn new(vault: &mut Vault, deltas: Vec<Delta>) -> Result<Self> {
let mut item = HistoryItem {
previous: vec![vault.metadata.current],
let mut commit = Commit {
previous: if vault.metadata.current == Hash::default() {
Vec::new()
} else {
vec![vault.metadata.current]
},
deltas: deltas,
hash: Hash::default(),
};
let data = serde_json::to_vec(&item).map_err(|e| io::Error::from(e))?;
let data = serde_json::to_vec(&commit).map_err(|e| io::Error::from(e))?;
let mut enc = Vec::new();
encrypt(data, &vault.key, &mut enc)?;
item.hash = Hash::new(&enc[..]);
commit.hash = Hash::new(&enc[..]);
let path = format!("{}.commit", commit.hash.to_hex());
create_umask()
.open(vault.path(
format!("sha3_256:{}.histitem", item.hash.to_hex()),
))?
.open(vault.path(&path))?
.write_all(&enc[..])?;
vault.modified = true;
vault.metadata.current = item.hash;
Ok(item)
vault.metadata.current = commit.hash;
vault.write_metadata()?;
info!("created new commit {:?}", path);
Ok(commit)
}

fn from_hash(vault: &Vault, hash: Hash) -> Result<Self> {
let mut f = File::open(vault.path(format!("sha3_256:{}.histitem", hash.to_hex())))?;
let path = format!("{}.commit", hash.to_hex());
let mut f = File::open(vault.path(&path))?;
let mut cont = Vec::new();
f.read_to_end(&mut cont)?;
let verifhash = Hash::new(&cont[..]);
@@ -212,17 +208,19 @@ impl HistoryItem {
));
}
let cont = decrypt(&vault.key, &cont[..])?;
let mut ret: HistoryItem = serde_json::from_slice(&cont[..]).map_err(
let mut ret: Commit = serde_json::from_slice(&cont[..]).map_err(
|e| io::Error::from(e),
)?;
ret.hash = hash;
debug!("loaded commit {:?}", path);
trace!("commit content: {}", String::from_utf8_lossy(&cont[..]));
Ok(ret)
}

pub fn previous(&self, vault: &Vault) -> Result<Vec<Self>> {
let mut ret = Vec::with_capacity(self.previous.len());
for h in &self.previous {
ret.push(HistoryItem::from_hash(vault, *h)?);
ret.push(Commit::from_hash(vault, *h)?);
}
Ok(ret)
}

+ 45
- 34
src/keyring.rs View File

@@ -2,9 +2,10 @@
use hex::ToHex;
use std::collections::HashMap;
use std::ops::Deref;
use std::mem::swap;

use crypto::Result;
use format::{Vault, HistoryItem, Delta};
use format::{Vault, Commit, Delta};

pub type Path = String;

@@ -19,36 +20,37 @@ pub struct Keyring(HashMap<Path, Entry>);

impl Keyring {
fn from_vault(vault: &Vault) -> Result<Self> {
if let Some(i) = vault.current_item()? {
Keyring::from_item(vault, i)
info!("constructing keyring");
if let Some(i) = vault.current_commit()? {
Keyring::from_commit(vault, i)
} else {
Ok(Keyring(HashMap::new()))
}
}

fn from_item(vault: &Vault, mut item: HistoryItem) -> Result<Self> {
debug!("constructing keyring from commit '{}'", item.hash.to_hex());
fn from_commit(vault: &Vault, mut commit: Commit) -> Result<Self> {
debug!("constructing keyring from commit {:?}", commit.hash.to_hex());
let mut hist_chain = vec![];
let mut previous = item.previous(vault)?;
let mut previous = commit.previous(vault)?;
while previous.len() == 1 {
hist_chain.push(item);
item = previous.pop().unwrap();
previous = item.previous(vault)?;
hist_chain.push(commit);
commit = previous.pop().unwrap();
previous = commit.previous(vault)?;
}
let mut kr = if previous.len() == 0 {
hist_chain.push(item);
hist_chain.push(commit);
Keyring(HashMap::new())
} else {
let primary = previous[0].hash.to_hex();
let secondaries: Vec<_> = previous[1..].iter().map(|i| i.hash.to_hex()).collect();
let mut previous_keyrings = Vec::with_capacity(previous.len());
debug!(
"commit '{}' is a merge of the following commits:",
"commit {:?} is a merge of the following commits:",
hist_chain[hist_chain.len() - 1].hash.to_hex()
);
for item in previous {
info!(" {}", item.hash.to_hex());
previous_keyrings.push((item.hash.to_hex(), Keyring::from_item(vault, item)?));
for commit in previous {
info!(" {}", commit.hash.to_hex());
previous_keyrings.push((commit.hash.to_hex(), Keyring::from_commit(vault, commit)?));
}
debug!("recursively reconstructing and renaming");
// Merges keep a copy of each predecessor, where <path> is moved to
@@ -67,7 +69,7 @@ impl Keyring {
})
.collect(),
);
kr.replay(item);
kr.replay(commit);
Keyring(
kr.0
.into_iter()
@@ -80,15 +82,15 @@ impl Keyring {
.collect::<HashMap<_, _>>(),
)
};
while let Some(item) = hist_chain.pop() {
kr.replay(item);
while let Some(commit) = hist_chain.pop() {
kr.replay(commit);
}
Ok(kr)
}

fn replay(&mut self, item: HistoryItem) {
debug!("replaying commit '{}'", item.hash.to_hex());
for delta in item.deltas {
fn replay(&mut self, commit: Commit) {
debug!("replaying commit {:?}", commit.hash.to_hex());
for delta in commit.deltas {
self.apply(&delta);
}
}
@@ -96,66 +98,75 @@ impl Keyring {
fn apply(&mut self, delta: &Delta) {
match delta {
&Delta::Insert(ref k, ref v) => {
trace!("insert({:?}, {:?})", k, v);
self.0.insert(k.clone(), Entry::Value(v.clone()));
}
&Delta::Rm(ref k) => {
trace!("rm({:?})", k);
if let None = self.0.remove(k) {
warn!("attempted rm '{}', but entry does not exist!", k);
warn!("attempted rm {:?}, but entry does not exist!", k);
}
}
&Delta::Mv(ref from, ref to) => {
trace!("mv({:?}, {:?})", from, to);
if let Some(v) = self.0.remove(from) {
self.0.insert(to.clone(), v);
} else {
warn!(
"attempted mv '{}' -> '{}', but entry does not exist!",
"attempted mv {:?} -> {:?}, but entry does not exist!",
from,
to
);
}
}
&Delta::Cp(ref from, ref to) => {
trace!("cp({:?}, {:?})", from, to);
if let Some(v) = self.0.remove(from) {
// A bit odd, but removing and re-adding avoids borrow issues.
self.0.insert(to.clone(), v.clone());
self.0.insert(from.clone(), v.clone());
} else {
warn!(
"attempted cp '{}' -> '{}', but entry does not exist!",
"attempted cp {:?} -> {:?}, but entry does not exist!",
from,
to
);
}
}
&Delta::Ln(ref target, ref link) => {
trace!("ln({:?}, {:?})", target, link);
self.0.insert(link.clone(), Entry::Ref(target.clone()));
}
}
}
}

pub struct KeyringState {
pub struct State {
vault: Vault,
keyring: Keyring,
deltas: Vec<Delta>,
}

impl KeyringState {
pub fn new(vault: Vault) -> Result<KeyringState> {
impl State {
pub fn new(vault: Vault) -> Result<Self> {
let kr = Keyring::from_vault(&vault)?;
Ok(KeyringState {
Ok(State {
vault: vault,
keyring: kr,
deltas: Vec::new(),
})
}

pub fn save(mut self) -> Result<Vault> {
HistoryItem::new(&mut self.vault, self.deltas)?;
Ok(self.vault)
pub fn commit(&mut self) -> Result<()> {
let mut deltas = Vec::new();
swap(&mut self.deltas, &mut deltas);
if deltas.len() > 0 {
Commit::new(&mut self.vault, deltas)?;
}
Ok(())
}

pub fn discard(self) -> Vault {
pub fn into_inner(self) -> Vault {
self.vault
}

@@ -165,10 +176,10 @@ impl KeyringState {
}
}

impl Deref for KeyringState {
type Target = Keyring;
impl Deref for State {
type Target = HashMap<Path, Entry>;

fn deref(&self) -> &Self::Target {
&self.keyring
&self.keyring.0
}
}

+ 19
- 2
src/main.rs View File

@@ -26,9 +26,26 @@ fn main() {
let f = PathBuf::from("foo.vault");
if f.exists() {
let vault = format::Vault::open(f, "foobar".as_bytes().to_owned()).unwrap();
vault.close().unwrap();
let krs = keyring::State::new(vault).unwrap();
for (k, v) in krs.iter() {
println!("'{}' => {}", k, match v {
&keyring::Entry::Value(ref s) => format!("'{}'", s),
&keyring::Entry::Ref(ref s) => format!("-> '{}'", s),
});
}
} else {
let vault = format::Vault::create(f, "foobar".as_bytes().to_owned()).unwrap();
vault.close().unwrap();
let mut krs = keyring::State::new(vault).unwrap();
//krs.apply(format::Delta::Insert("foo".to_owned(), "bar".to_owned()));
//krs.apply(format::Delta::Insert("bar".to_owned(), "baz".to_owned()));
use rand::{thread_rng, Rng};
for _ in 0..10000 {
for _ in 0..5 {
let k = thread_rng().gen_ascii_chars().take(10).collect();
let v = thread_rng().gen_ascii_chars().take(10).collect();
krs.apply(format::Delta::Insert(k, v));
}
krs.commit().unwrap();
}
}
}

Loading…
Cancel
Save