use base64::Engine;
use super::CHECKSUM;
use super::keyspace::Keyspace;
use crate::error::Error;
const BODY: &[u8] = b"lfsx probe";
fn probe_key(what: &str) -> String {
let mut suffix = [0u8; 8];
getrandom::fill(&mut suffix).expect("the operating system has a random number generator");
format!(".probe/{what}-{}", hex::encode(suffix))
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) enum Checksums {
Enforced,
Ignored,
Unknown,
}
fn wrong_digest() -> String {
base64::engine::general_purpose::STANDARD.encode([0u8; 32])
}
pub(crate) async fn checksums(keys: &Keyspace) -> Checksums {
let key = probe_key("checksum");
let signed = keys.signed_upload(&key, vec![(CHECKSUM.to_owned(), wrong_digest())]);
let mut request = keys.client().put(&signed.href).body(BODY.to_vec());
for (name, value) in &signed.headers {
request = request.header(name, value);
}
if let Err(error) = request.send().await {
tracing::warn!(%error, "the object store could not be reached to check it verifies uploads");
return Checksums::Unknown;
}
match keys.head(&key).await {
Err(Error::NotFound) => Checksums::Enforced,
Err(error) => {
tracing::warn!(%error, "the object store could not say whether it kept the probe");
discard(keys, &key).await;
Checksums::Unknown
}
Ok(_) => {
discard(keys, &key).await;
Checksums::Ignored
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) enum Conditional {
Enforced,
Ignored,
Unknown,
}
pub(crate) async fn conditional_writes(keys: &Keyspace) -> Conditional {
let key = probe_key("conditional");
match keys.put_if_absent(&key, BODY.to_vec()).await {
Ok(true) => {}
Ok(false) => {
tracing::warn!(
key,
"the object store refused the first write to a key it had never seen, so whether \
it refuses a conditional write was not established"
);
return Conditional::Unknown;
}
Err(error) => {
tracing::warn!(%error, "the object store could not be reached to check it refuses a conditional write");
return Conditional::Unknown;
}
}
let verdict = match keys.put_if_absent(&key, BODY.to_vec()).await {
Ok(false) => Conditional::Enforced,
Ok(true) => Conditional::Ignored,
Err(error) => {
tracing::warn!(%error, "the object store gave no usable answer to a conditional write");
Conditional::Unknown
}
};
discard(keys, &key).await;
verdict
}
async fn discard(keys: &Keyspace, key: &str) {
if let Err(error) = keys.delete(key).await {
tracing::warn!(
%error,
key,
"the probe object could not be cleaned up, and is left for the reclaimer"
);
}
}
#[cfg(test)]
mod tests;