summary refs log tree commit diff
path: root/ripple/fossil/src/lib.rs
diff options
context:
space:
mode:
Diffstat (limited to 'ripple/fossil/src/lib.rs')
-rw-r--r--ripple/fossil/src/lib.rs40
1 files changed, 39 insertions, 1 deletions
diff --git a/ripple/fossil/src/lib.rs b/ripple/fossil/src/lib.rs
index 53e4d7b..aa4821c 100644
--- a/ripple/fossil/src/lib.rs
+++ b/ripple/fossil/src/lib.rs
@@ -3,7 +3,7 @@
 
 pub use crate::chunker::Chunker;
 use {
-	anyhow::{Context, Result},
+	anyhow::{anyhow, bail, Context, Result},
 	byteorder::{BigEndian, ByteOrder},
 	prost::Message,
 	sled::{transaction::ConflictableTransactionError, Transactional},
@@ -474,6 +474,44 @@ impl Directory {
 	}
 }
 
+pub fn digest_str(digest: &Digest) -> String {
+	let bytes = digest.as_bytes();
+	zbase32::encode_full_bytes(bytes)
+}
+
+pub fn digest_from_str(s: &str) -> Result<Digest> {
+	let bits = DIGEST_BYTES * 8;
+	let chars = (bits + 5 - 1) / 5;
+
+	if s.len() < chars {
+		bail!("digest too short");
+	}
+
+	let bytes_vec = zbase32::decode_str(s, bits as u64).map_err(|_| anyhow!("invalid digest"))?;
+	if zbase32::encode_full_bytes(&bytes_vec) != s {
+		// non-canonical input
+		bail!("non-canonical digest");
+	}
+
+	let mut bytes = [0; DIGEST_BYTES];
+	bytes.copy_from_slice(&bytes_vec);
+
+	Ok(bytes.into())
+}
+
+#[test]
+fn digest_str_golden() {
+	let h = blake3::hash(b"hello fossil!");
+	let s = "xow7w4moszx4w5ngp7d7w3fyfk313tpw6fndik4imdhd18ynw6so";
+	assert_eq!(digest_str(&h), s);
+	assert_eq!(digest_from_str(s).ok(), Some(h));
+}
+
+#[test]
+fn digest_str_short() {
+	assert!(digest_from_str("xow7w4moszx4w5ngp7d7w3fyfk313tpw6fndik4imdhd18ynw6s").is_err());
+}
+
 #[track_caller]
 pub fn digest_from_bytes(bytes: &[u8]) -> Digest {
 	if bytes.len() != DIGEST_BYTES {