Merge pull request 'Blob support' (#30) from get_blob into main
Reviewed-on: #30
This commit is contained in:
commit
7b78274a52
|
@ -223,6 +223,15 @@ version = "1.3.2"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
|
||||
|
||||
[[package]]
|
||||
name = "block-buffer"
|
||||
version = "0.10.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0bf7fe51849ea569fd452f37822f606a5cabb684dc918707a0193fd4664ff324"
|
||||
dependencies = [
|
||||
"generic-array",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "blocking"
|
||||
version = "1.1.0"
|
||||
|
@ -282,6 +291,15 @@ dependencies = [
|
|||
"cache-padded",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cpufeatures"
|
||||
version = "0.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "95059428f66df56b63431fdb4e1947ed2190586af5c5a8a8b71122bdf5a7f469"
|
||||
dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "crossbeam-utils"
|
||||
version = "0.8.7"
|
||||
|
@ -292,6 +310,16 @@ dependencies = [
|
|||
"lazy_static",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "crypto-common"
|
||||
version = "0.1.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "57952ca27b5e3606ff4dd79b0020231aaf9d6aa76dc05fd30137538c50bd3ce8"
|
||||
dependencies = [
|
||||
"generic-array",
|
||||
"typenum",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ctor"
|
||||
version = "0.1.21"
|
||||
|
@ -302,6 +330,16 @@ dependencies = [
|
|||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "digest"
|
||||
version = "0.10.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f2fb860ca6fafa5552fb6d0e816a69c8e49f0908bf524e30a90d97c85892d506"
|
||||
dependencies = [
|
||||
"block-buffer",
|
||||
"crypto-common",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "dirs"
|
||||
version = "2.0.2"
|
||||
|
@ -448,6 +486,16 @@ version = "0.3.55"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8f5f3913fa0bfe7ee1fd8248b6b9f42a5af4b9d65ec2dd2c3c26132b950ecfc2"
|
||||
|
||||
[[package]]
|
||||
name = "generic-array"
|
||||
version = "0.14.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fd48d33ec7f05fbfa152300fdad764757cbded343c1aa1cff2fbaf4134851803"
|
||||
dependencies = [
|
||||
"typenum",
|
||||
"version_check",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "get_if_addrs"
|
||||
version = "0.5.3"
|
||||
|
@ -507,6 +555,7 @@ dependencies = [
|
|||
"kuska-ssb",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"sha2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -814,6 +863,17 @@ dependencies = [
|
|||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "sha2"
|
||||
version = "0.10.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "55deaec60f81eefe3cce0dc50bda92d6d8e88f2a27df7c5033b42afeb1ed2676"
|
||||
dependencies = [
|
||||
"cfg-if 1.0.0",
|
||||
"cpufeatures",
|
||||
"digest",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "signal-hook"
|
||||
version = "0.3.13"
|
||||
|
@ -880,6 +940,12 @@ dependencies = [
|
|||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "typenum"
|
||||
version = "1.15.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "dcf81ac59edc17cc8697ff311e8f5ef2d99fcbd9817b34cec66f90b6c3dfd987"
|
||||
|
||||
[[package]]
|
||||
name = "unicode-xid"
|
||||
version = "0.2.2"
|
||||
|
|
|
@ -22,3 +22,4 @@ kuska-sodiumoxide = "0.2.5-0"
|
|||
kuska-ssb = "0.4.0"
|
||||
serde = { version = "1", features = ["derive"] }
|
||||
serde_json = "1"
|
||||
sha2 = "0.10.2"
|
||||
|
|
|
@ -4,9 +4,12 @@
|
|||
//!
|
||||
//! - [`Sbot::publish`]
|
||||
//! - [`Sbot::publish_description`]
|
||||
//! - [`Sbot::publish_image`]
|
||||
//! - [`Sbot::publish_name`]
|
||||
//! - [`Sbot::publish_post`]
|
||||
|
||||
use kuska_ssb::api::dto::content::Image;
|
||||
|
||||
use crate::{error::GolgiError, messages::SsbMessageContent, sbot::Sbot, utils};
|
||||
|
||||
impl Sbot {
|
||||
|
@ -116,6 +119,45 @@ impl Sbot {
|
|||
self.publish(msg).await
|
||||
}
|
||||
|
||||
/// Publish an image for the local identity.
|
||||
///
|
||||
/// Requires the image to have been added to the local blobstore. The
|
||||
/// ID of the blob (sigil-link in the form `&...=.sha256`) must be provided.
|
||||
///
|
||||
/// Convenient wrapper around the `publish` method which constructs and
|
||||
/// publishes an `about` type name message appropriately from a string.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```rust
|
||||
/// use golgi::{Sbot, GolgiError};
|
||||
///
|
||||
/// async fn publish_an_image() -> Result<(), GolgiError> {
|
||||
/// let mut sbot_client = Sbot::init(None, None).await?;
|
||||
///
|
||||
/// let blob_id = "&JlJHc9yeG1EpZA9fIPGLzUKDH0FeR39Ai57euhKT1G8=.sha256";
|
||||
///
|
||||
/// let msg_ref = sbot_client.publish_image(blob_id).await?;
|
||||
///
|
||||
/// println!("msg reference: {}", msg_ref);
|
||||
///
|
||||
/// Ok(())
|
||||
/// }
|
||||
/// ```
|
||||
pub async fn publish_image(&mut self, blob_id: &str) -> Result<String, GolgiError> {
|
||||
let msg = SsbMessageContent::About {
|
||||
about: self.id.to_string(),
|
||||
name: None,
|
||||
title: None,
|
||||
branch: None,
|
||||
image: Some(Image::OnlyLink(blob_id.to_string())),
|
||||
description: None,
|
||||
location: None,
|
||||
start_datetime: None,
|
||||
};
|
||||
self.publish(msg).await
|
||||
}
|
||||
|
||||
/// Publish a name for the local identity.
|
||||
///
|
||||
/// Convenient wrapper around the `publish` method which constructs and
|
||||
|
|
|
@ -0,0 +1,145 @@
|
|||
//! Blob utilities which do not require RPC calls.
|
||||
//!
|
||||
//! Offers the following functions:
|
||||
//!
|
||||
//! - [`get_blob_path()`]
|
||||
//! - [`hash_blob()`]
|
||||
|
||||
use base64;
|
||||
use sha2::{Digest, Sha256};
|
||||
|
||||
use crate::error::GolgiError;
|
||||
|
||||
/// Lookup the filepath for a blob.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```rust
|
||||
/// use golgi::{blobs, GolgiError};
|
||||
///
|
||||
/// 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> {
|
||||
// ensure the id starts with the correct sigil link
|
||||
if !blob_id.starts_with('&') {
|
||||
return Err(GolgiError::SigilLink(format!(
|
||||
"incorrect first character, expected '&' sigil: {}",
|
||||
blob_id
|
||||
)));
|
||||
}
|
||||
|
||||
// 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)))?;
|
||||
|
||||
// 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)
|
||||
}
|
||||
|
||||
/// 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))
|
||||
}
|
||||
|
||||
#[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!(),
|
||||
}
|
||||
}
|
||||
}
|
|
@ -34,9 +34,11 @@ pub enum GolgiError {
|
|||
Rpc(RpcError),
|
||||
/// Go-sbot error.
|
||||
Sbot(String),
|
||||
/// SSB sigil-link error.
|
||||
SigilLink(String),
|
||||
/// JSON serialization or deserialization error.
|
||||
SerdeJson(JsonError),
|
||||
/// Error decoding typed ssb message from content.
|
||||
/// Error decoding typed SSB message from content.
|
||||
ContentType(String),
|
||||
/// Error decoding UTF8 string from bytes
|
||||
Utf8Parse {
|
||||
|
@ -55,6 +57,7 @@ impl std::error::Error for GolgiError {
|
|||
GolgiError::Feed(ref err) => Some(err),
|
||||
GolgiError::Rpc(ref err) => Some(err),
|
||||
GolgiError::Sbot(_) => None,
|
||||
GolgiError::SigilLink(_) => None,
|
||||
GolgiError::SerdeJson(ref err) => Some(err),
|
||||
GolgiError::ContentType(_) => None,
|
||||
GolgiError::Utf8Parse { ref source } => Some(source),
|
||||
|
@ -75,11 +78,12 @@ impl std::fmt::Display for GolgiError {
|
|||
// then have the core display msg be: "SSB RPC error: {}", context
|
||||
GolgiError::Rpc(ref err) => write!(f, "SSB RPC failure: {}", err),
|
||||
GolgiError::Sbot(ref err) => write!(f, "Sbot returned an error response: {}", err),
|
||||
GolgiError::SigilLink(ref context) => write!(f, "SSB blob ID error: {}", context),
|
||||
GolgiError::SerdeJson(_) => write!(f, "Failed to serialize JSON slice"),
|
||||
//GolgiError::WhoAmI(ref err) => write!(f, "{}", err),
|
||||
GolgiError::ContentType(ref err) => write!(
|
||||
f,
|
||||
"Failed to decode typed message from ssb message content: {}",
|
||||
"Failed to decode typed message from SSB message content: {}",
|
||||
err
|
||||
),
|
||||
GolgiError::Utf8Parse { source } => {
|
||||
|
|
|
@ -69,6 +69,7 @@
|
|||
//! ```
|
||||
|
||||
pub mod api;
|
||||
pub mod blobs;
|
||||
pub mod error;
|
||||
pub mod messages;
|
||||
pub mod sbot;
|
||||
|
|
Loading…
Reference in New Issue