Compare commits

...

2 commits

Author SHA1 Message Date
89f5d26cb3 implement better hashing
- use `blake2b_simd` crate and optimize
2025-06-24 19:07:22 +00:00
6e553cc185 implement better hashing
- use Blake2b128 for the CLI arguments hash
2025-06-24 16:29:38 +00:00
6 changed files with 91 additions and 31 deletions

View file

@ -1,2 +1,6 @@
[build] [build]
target = "x86_64-unknown-linux-musl" target = "x86_64-unknown-linux-musl"
rustflags = [
"-C", "target-feature=+avx2,+sse4.1,+ssse3,+aes",
]

37
Cargo.lock generated
View file

@ -67,18 +67,47 @@ dependencies = [
"windows-sys 0.59.0", "windows-sys 0.59.0",
] ]
[[package]]
name = "arrayref"
version = "0.3.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "76a2e8124351fda1ef8aaaa3bbd7ebbcb486bbcd4225aca0aa0d84bb2db8fecb"
[[package]]
name = "arrayvec"
version = "0.7.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50"
[[package]] [[package]]
name = "base64" name = "base64"
version = "0.22.1" version = "0.22.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6"
[[package]]
name = "base64ct"
version = "1.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "55248b47b0caf0546f7988906588779981c43bb1bc9d0c44087278f80cdb44ba"
[[package]] [[package]]
name = "bitflags" name = "bitflags"
version = "2.9.1" version = "2.9.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1b8e56985ec62d17e9c1001dc89c88ecd7dc08e47eba5ec7c29c7b5eeecde967" checksum = "1b8e56985ec62d17e9c1001dc89c88ecd7dc08e47eba5ec7c29c7b5eeecde967"
[[package]]
name = "blake2b_simd"
version = "1.0.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "06e903a20b159e944f91ec8499fe1e55651480c541ea0a584f5d967c49ad9d99"
dependencies = [
"arrayref",
"arrayvec",
"constant_time_eq",
]
[[package]] [[package]]
name = "bumpalo" name = "bumpalo"
version = "3.17.0" version = "3.17.0"
@ -171,6 +200,12 @@ dependencies = [
"windows-sys 0.59.0", "windows-sys 0.59.0",
] ]
[[package]]
name = "constant_time_eq"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7c74b8349d32d297c9134b8c88677813a227df8f779daa29bfc29c183fe3dca6"
[[package]] [[package]]
name = "cookie" name = "cookie"
version = "0.18.1" version = "0.18.1"
@ -846,6 +881,8 @@ checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64"
name = "shrupl" name = "shrupl"
version = "0.1.0-alpha" version = "0.1.0-alpha"
dependencies = [ dependencies = [
"base64ct",
"blake2b_simd",
"clap", "clap",
"console", "console",
"ctrlc", "ctrlc",

View file

@ -5,6 +5,8 @@ edition = "2024"
description = "ShrUpl is a tool to upload files to a Sharry Instance through a public Alias, leveraging the tus protocol" description = "ShrUpl is a tool to upload files to a Sharry Instance through a public Alias, leveraging the tus protocol"
[dependencies] [dependencies]
base64ct = { version = "1.8.0", default-features = false, features = ["alloc"] }
blake2b_simd = "1.0.3"
clap = { version = "4.5.38", features = ["derive"] } clap = { version = "4.5.38", features = ["derive"] }
console = { version = "0.15.11", default-features = false } console = { version = "0.15.11", default-features = false }
ctrlc = { version = "3.4.7", features = ["termination"] } ctrlc = { version = "3.4.7", features = ["termination"] }
@ -20,5 +22,11 @@ thiserror = "2.0.12"
ureq = { version = "3.0.11", features = ["json"] } ureq = { version = "3.0.11", features = ["json"] }
[profile.release] [profile.release]
# Optimize for speed even more aggressively
opt-level = "z"
# better inlining
codegen-units = 1
# linkertime optimization
lto = true lto = true
debug = false
panic = "abort" panic = "abort"

View file

@ -1,11 +1,7 @@
use std::{ use std::{convert::Infallible, fmt, io, time::Duration};
convert::Infallible,
fmt,
hash::{DefaultHasher, Hash, Hasher},
io,
time::Duration,
};
use base64ct::{Base64UrlUnpadded, Encoding};
use blake2b_simd::Params as Blake2b;
use clap::{ use clap::{
Parser, Parser,
builder::{PossibleValuesParser, TypedValueParser}, builder::{PossibleValuesParser, TypedValueParser},
@ -18,7 +14,7 @@ use crate::{
sharry::{NewShareRequest, Uri}, sharry::{NewShareRequest, Uri},
}; };
#[derive(Parser, Hash)] #[derive(Parser)]
#[command(version, about, long_about = None)] #[command(version, about, long_about = None)]
pub struct Cli { pub struct Cli {
/// Timeout in seconds for HTTP actions (set 0 or invalid to disable) /// Timeout in seconds for HTTP actions (set 0 or invalid to disable)
@ -100,6 +96,16 @@ fn parse_sharry_file(data: &str) -> io::Result<Checked> {
Checked::new(data) Checked::new(data)
} }
fn sorted<T>(values: &[T]) -> Vec<&T>
where
T: Ord,
{
let mut refs: Vec<_> = values.iter().collect();
refs.sort_unstable();
refs
}
impl Cli { impl Cli {
pub fn get_timeout(&self) -> Option<Duration> { pub fn get_timeout(&self) -> Option<Duration> {
(!self.timeout.is_zero()).then_some(self.timeout) (!self.timeout.is_zero()).then_some(self.timeout)
@ -135,16 +141,15 @@ impl Cli {
} }
pub fn get_hash(&self) -> String { pub fn get_hash(&self) -> String {
let file_refs = { let mut hasher = Blake2b::new().hash_length(64).to_state();
let mut refs: Vec<_> = self.files.iter().collect();
refs.sort_unstable();
refs hasher.update(self.get_uri().as_ref());
}; hasher.update(self.alias.as_bytes());
let mut hasher = DefaultHasher::new(); for chk in sorted(&self.files) {
(self.get_uri(), &self.alias, file_refs).hash(&mut hasher); hasher.update(chk.as_ref());
}
format!("{:x}", hasher.finish()) Base64UrlUnpadded::encode_string(hasher.finalize().as_bytes())
} }
} }

View file

@ -11,10 +11,10 @@ use super::{FileTrait, Uploading};
/// Description of an existing, regular file /// Description of an existing, regular file
/// ///
/// - impl Debug, Clone, Hash for `clap` compatibility /// - impl Clone for `clap` compatibility
/// - impl serde for appstate caching /// - impl serde for appstate caching
/// - impl Ord to handle multiple files given /// - impl PartialEq..Ord to handle multiple files given
#[derive(Debug, Clone, Hash, Serialize, Deserialize, PartialEq, Eq, PartialOrd, Ord)] #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, PartialOrd, Ord)]
pub struct Checked { pub struct Checked {
/// canonical path to a regular file /// canonical path to a regular file
pub(super) path: PathBuf, pub(super) path: PathBuf,
@ -22,6 +22,12 @@ pub struct Checked {
pub(super) size: u64, pub(super) size: u64,
} }
impl AsRef<[u8]> for Checked {
fn as_ref(&self) -> &[u8] {
self.path.as_os_str().as_encoded_bytes()
}
}
impl Checked { impl Checked {
/// create a new checked file from some path reference /// create a new checked file from some path reference
/// ///

View file

@ -3,28 +3,28 @@ use std::fmt;
use log::trace; use log::trace;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
#[derive(Serialize, Deserialize, Debug, Hash)] #[derive(Serialize, Deserialize, Debug)]
pub struct Uri { pub struct Uri(String);
protocol: String,
base_url: String,
}
impl fmt::Display for Uri { impl fmt::Display for Uri {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}://{}", self.protocol, self.base_url) f.write_str(&self.0)
}
}
impl AsRef<[u8]> for Uri {
fn as_ref(&self) -> &[u8] {
self.0.as_bytes()
} }
} }
impl Uri { impl Uri {
pub fn new(protocol: impl Into<String>, base_url: impl Into<String>) -> Self { pub fn new(protocol: impl fmt::Display, base_url: impl fmt::Display) -> Self {
Self { Self(format!("{}://{}", protocol, base_url))
protocol: protocol.into(),
base_url: base_url.into(),
}
} }
fn endpoint(&self, path: fmt::Arguments) -> String { fn endpoint(&self, path: fmt::Arguments) -> String {
let uri = format!("{}://{}/api/v2/{path}", self.protocol, self.base_url); let uri = format!("{}/api/v2/{path}", self.0);
trace!("endpoint: {uri:?}"); trace!("endpoint: {uri:?}");
uri uri
} }