use crate::piv::PivBackend;
use crate::store::{Object, ObjectParams, Store};
use sha2::{Digest, Sha256};
pub(super) fn append_signature_trailer(
store: &mut Store,
piv: &dyn PivBackend,
payload: &[u8],
pin: Option<&str>,
) {
let digest = Sha256::digest(payload);
let raw_sig = match piv.ecdsa_sign(&store.reader, store.store_key_slot, &digest, pin) {
Ok(s) => s,
Err(e) => {
eprintln!(
"yb: warning: ECDSA sign failed, storing blob without integrity signature: {e}"
);
return;
}
};
let mut trailer = [0u8; 65];
trailer[0] = 0x01; trailer[1..33].copy_from_slice(&raw_sig[0..32]); trailer[33..65].copy_from_slice(&raw_sig[32..64]);
let last_idx = find_last_chunk_idx(store);
let last_obj = &store.objects[last_idx as usize];
let last_max_payload = if last_obj.chunk_pos == 0 {
Object::head_payload_capacity(store.object_size, last_obj.blob_name.len())
} else {
Object::continuation_payload_capacity(store.object_size)
};
let last_payload_len = last_obj.payload.len();
let space_in_last = last_max_payload.saturating_sub(last_payload_len);
if space_in_last >= trailer.len() {
store.objects[last_idx as usize]
.payload
.extend_from_slice(&trailer);
store.objects[last_idx as usize].dirty = true;
} else {
if store.free_count() == 0 {
eprintln!("yb: warning: store full, cannot add signature spill slot — storing blob without integrity signature");
return;
}
store.objects[last_idx as usize]
.payload
.extend_from_slice(&trailer[..space_in_last]);
store.objects[last_idx as usize].dirty = true;
let spill_idx = store.alloc_free().unwrap();
let age = store.next_age();
let chunk_pos = store.objects[last_idx as usize].chunk_pos + 1;
let mut spill_obj = store.make_object(ObjectParams {
index: spill_idx,
age,
chunk_pos,
next_chunk: spill_idx, });
spill_obj.payload = trailer[space_in_last..].to_vec();
store.objects[spill_idx as usize] = spill_obj;
store.objects[last_idx as usize].next_chunk = spill_idx;
store.objects[last_idx as usize].dirty = true;
}
}
pub(super) fn find_last_chunk_idx(store: &Store) -> u8 {
let head = store
.objects
.iter()
.filter(|o| o.is_head())
.max_by_key(|o| o.age)
.expect("store must have at least one head after store_blob");
let chain = store.chunk_chain(head.index);
*chain.last().expect("chain must be non-empty")
}
pub fn collect_blob_chain(
head: &crate::store::Object,
store: &crate::store::Store,
) -> (Vec<u8>, Vec<u8>, bool) {
let blob_size = head.blob_size as usize;
let chain = store.chunk_chain(head.index);
let mut all_bytes: Vec<u8> = Vec::new();
let mut combined_without_last = 0usize;
for (i, &idx) in chain.iter().enumerate() {
let slot_bytes = &store.objects[idx as usize].payload;
if i + 1 < chain.len() {
combined_without_last += slot_bytes.len();
}
all_bytes.extend_from_slice(slot_bytes);
}
let has_supernumerary_slot = combined_without_last >= blob_size;
let payload = all_bytes[..blob_size.min(all_bytes.len())].to_vec();
let trailing = if all_bytes.len() > blob_size {
all_bytes[blob_size..].to_vec()
} else {
vec![]
};
(payload, trailing, has_supernumerary_slot)
}