2022-02-24 09:02:21 +00:00
|
|
|
//! Blob utilities which do not require RPC calls.
|
|
|
|
//!
|
|
|
|
//! Offers the following functions:
|
|
|
|
//!
|
|
|
|
//! - [`get_blob_path()`]
|
2022-02-24 17:43:28 +00:00
|
|
|
//! - [`hash_blob()`]
|
|
|
|
|
|
|
|
use base64;
|
|
|
|
use sha2::{Digest, Sha256};
|
2022-02-24 09:02:21 +00:00
|
|
|
|
|
|
|
use crate::error::GolgiError;
|
|
|
|
|
|
|
|
/// Lookup the filepath for a blob.
|
|
|
|
///
|
|
|
|
/// # Example
|
|
|
|
///
|
|
|
|
/// ```rust
|
2022-02-25 06:52:26 +00:00
|
|
|
/// use golgi::{blobs, GolgiError};
|
2022-02-24 09:02:21 +00:00
|
|
|
///
|
|
|
|
/// fn lookup_blob_path() -> Result<(), GolgiError> {
|
|
|
|
/// let blob_ref = "&JlJHc9yeG1EpZA9fIPGLzUKDH0FeR39Ai57euhKT1G8=.sha256";
|
|
|
|
///
|
|
|
|
/// let blob_path = blobs::get_blob_path(blob_ref)?;
|
|
|
|
///
|
|
|
|
/// println!("{}", blob_path);
|
|
|
|
///
|
|
|
|
/// // 26/524773dc9e1b5129640f5f20f18bcd42831f415e477f408b9edeba1293d46f
|
|
|
|
///
|
|
|
|
/// Ok(())
|
|
|
|
/// }
|
|
|
|
/// ```
|
|
|
|
pub fn get_blob_path(blob_id: &str) -> Result<String, GolgiError> {
|
2022-02-25 06:52:26 +00:00
|
|
|
// ensure the id starts with the correct sigil link
|
|
|
|
if !blob_id.starts_with('&') {
|
|
|
|
return Err(GolgiError::SigilLink(format!(
|
|
|
|
"incorrect first character, expected '&' sigil: {}",
|
2022-02-24 09:02:21 +00:00
|
|
|
blob_id
|
2022-02-25 06:52:26 +00:00
|
|
|
)));
|
|
|
|
}
|
|
|
|
|
|
|
|
// find the dot index denoting the start of the algorithm definition tag
|
|
|
|
let dot_index = blob_id
|
|
|
|
.rfind('.')
|
|
|
|
.ok_or_else(|| GolgiError::SigilLink(format!("no dot index was found: {}", blob_id)))?;
|
2022-02-24 09:02:21 +00:00
|
|
|
|
|
|
|
// obtain the base64 portion (substring) of the blob id
|
|
|
|
let base64_str = &blob_id[1..dot_index];
|
|
|
|
|
|
|
|
// decode blob substring from base64 (to bytes)
|
|
|
|
let blob_bytes = base64::decode_config(base64_str, base64::STANDARD)?;
|
|
|
|
|
|
|
|
// represent the blob bytes as hex, removing all unnecessary characters
|
|
|
|
let blob_hex = format!("{:02x?}", blob_bytes)
|
|
|
|
.replace('[', "")
|
|
|
|
.replace(']', "")
|
|
|
|
.replace(',', "")
|
|
|
|
.replace(' ', "");
|
|
|
|
|
|
|
|
// split the hex representation of the decoded base64
|
|
|
|
// this is how paths are formatted for the blobstore
|
|
|
|
// e.g. 26/524773dc9e1b5129640f5f20f18bcd42831f415e477f408b9edeba1293d46f
|
|
|
|
// full path would be: `/home/user/.ssb-go/blobs/sha256/26/524773dc...`
|
|
|
|
let blob_path = format!("{}/{}", &blob_hex[..2], &blob_hex[2..]);
|
|
|
|
|
|
|
|
Ok(blob_path)
|
|
|
|
}
|
2022-02-24 17:43:28 +00:00
|
|
|
|
|
|
|
/// Hash the given blob byte slice and return the hex representation and
|
|
|
|
/// blob ID (sigil-link).
|
|
|
|
///
|
|
|
|
/// This function can be used when adding a blob to the local blobstore.
|
|
|
|
pub fn hash_blob(buffer: &[u8]) -> Result<(String, String), GolgiError> {
|
|
|
|
// hash the bytes
|
|
|
|
let hash = Sha256::digest(buffer);
|
|
|
|
|
|
|
|
// generate a hex representation of the hash
|
|
|
|
let hex_hash = format!("{:02x?}", hash)
|
|
|
|
.replace('[', "")
|
|
|
|
.replace(']', "")
|
|
|
|
.replace(',', "")
|
|
|
|
.replace(' ', "");
|
|
|
|
|
|
|
|
// encode the hash
|
|
|
|
let b64_hash = base64::encode(&hash[..]);
|
|
|
|
|
|
|
|
// format the base64 encoding as a blob sigil-link (blob id)
|
|
|
|
let blob_id = format!("&{}.sha256", b64_hash);
|
|
|
|
|
|
|
|
Ok((hex_hash, blob_id))
|
|
|
|
}
|
2022-02-25 06:52:26 +00:00
|
|
|
|
|
|
|
#[cfg(test)]
|
|
|
|
mod tests {
|
|
|
|
use crate::blobs;
|
|
|
|
|
|
|
|
/* HAPPY PATH TESTS */
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn blob_path() {
|
|
|
|
let blob_ref = "&JlJHc9yeG1EpZA9fIPGLzUKDH0FeR39Ai57euhKT1G8=.sha256";
|
|
|
|
let blob_path = blobs::get_blob_path(blob_ref);
|
|
|
|
assert!(blob_path.is_ok());
|
|
|
|
assert_eq!(
|
|
|
|
blob_path.unwrap(),
|
|
|
|
"26/524773dc9e1b5129640f5f20f18bcd42831f415e477f408b9edeba1293d46f"
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn hashed_blob() {
|
|
|
|
// pretend this represents a file which has been written to a buffer
|
|
|
|
let buffer = vec![7; 128];
|
|
|
|
let hash_result = blobs::hash_blob(&buffer);
|
|
|
|
assert!(hash_result.is_ok());
|
|
|
|
let (hex_hash, blob_id) = hash_result.unwrap();
|
|
|
|
assert_eq!(
|
|
|
|
hex_hash,
|
|
|
|
"4c1398e54d53e925cff04da532f4bbaf15f75b5981fc76c2072dfdc6491a9fb1"
|
|
|
|
);
|
|
|
|
assert_eq!(
|
|
|
|
blob_id,
|
|
|
|
"&TBOY5U1T6SXP8E2lMvS7rxX3W1mB/HbCBy39xkkan7E=.sha256"
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
/* SAD PATH TESTS */
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn blob_id_without_sigil() {
|
|
|
|
let blob_ref = "JlJHc9yeG1EpZA9fIPGLzUKDH0FeR39Ai57euhKT1G8=.sha256";
|
|
|
|
match blobs::get_blob_path(blob_ref) {
|
|
|
|
Err(e) => assert_eq!(e.to_string(), "SSB blob ID error: incorrect first character, expected '&' sigil: JlJHc9yeG1EpZA9fIPGLzUKDH0FeR39Ai57euhKT1G8=.sha256"),
|
|
|
|
_ => panic!(),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn blob_id_without_algo() {
|
|
|
|
let blob_ref = "&JlJHc9yeG1EpZA9fIPGLzUKDH0FeR39Ai57euhKT1G8=";
|
|
|
|
match blobs::get_blob_path(blob_ref) {
|
|
|
|
Err(e) => assert_eq!(e.to_string(), "SSB blob ID error: no dot index was found: &JlJHc9yeG1EpZA9fIPGLzUKDH0FeR39Ai57euhKT1G8="),
|
|
|
|
_ => panic!(),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|