use async_trait::async_trait;
use redb::{ReadableDatabase, ReadableTable, TableDefinition};
use crate::core::{Attestation, Digest, KeyId, Signer, StoreError, Verifier};
use crate::manifest::registry::{
PublishVerdict, attest_manifest, check_attestation, decide_publish, reparse, to_yaml,
};
use crate::manifest::{Manifest, Registry, RegistryError};
use super::redb::{MAX_STR, RedbStore, be, begin_write};
type RegistryRow<'a> = (&'a str, &'a str, &'a str, &'a str);
const MANIFESTS: TableDefinition<(&str, &str, &str), RegistryRow<'static>> =
TableDefinition::new("registry_manifests");
fn stored_attestation(key_id: &str, signature: &str) -> Option<Attestation> {
if key_id.is_empty() {
return None;
}
Some(Attestation {
key_id: key_id.to_owned(),
signature: hex::decode(signature).unwrap_or_default(),
})
}
type Stored = Option<(Digest, Option<Attestation>)>;
fn backend(e: &StoreError) -> RegistryError {
RegistryError::Backend(e.to_string())
}
impl RedbStore {
async fn publish_row(
&self,
manifest: &Manifest,
attestation: Option<Attestation>,
) -> Result<Digest, RegistryError> {
let tenant = self.tenant_name();
let name = manifest.metadata.name.clone();
let version = manifest.metadata.version.clone();
let digest = manifest.digest().map_err(|source| RegistryError::Corrupt {
name: name.clone(),
version: version.clone(),
source,
})?;
let yaml = to_yaml(manifest)?;
let signed = attestation
.as_ref()
.map_or((String::new(), String::new()), |a| {
(a.key_id.clone(), hex::encode(&a.signature))
});
let outcome: Result<(), RegistryError> = self
.with_db(move |db| {
let w = begin_write(db)?;
let key = (tenant.as_str(), name.as_str(), version.as_str());
let decided = {
let mut table = w.open_table(MANIFESTS).map_err(|e| be(&e))?;
let stored: Stored = match table.get(key).map_err(|e| be(&e))? {
Some(row) => {
let (hex, _, key_id, signature) = row.value();
let digest =
Digest::from_hex(hex).map_err(|_| StoreError::Corrupt {
seq: 0,
detail: format!(
"registry row '{name}' '{version}' holds '{hex}', \
which is not a digest"
),
})?;
Some((digest, stored_attestation(key_id, signature)))
}
None => None,
};
let verdict = decide_publish(
&name,
&version,
digest,
attestation.as_ref(),
stored.as_ref().map(|(d, a)| (*d, a.as_ref())),
);
match verdict {
Ok(PublishVerdict::Insert | PublishVerdict::AdoptAttestation) => {
table
.insert(
key,
(
digest.to_hex().as_str(),
yaml.as_str(),
signed.0.as_str(),
signed.1.as_str(),
),
)
.map_err(|e| be(&e))?;
Ok(())
}
Ok(PublishVerdict::Unchanged) => Ok(()),
Err(refusal) => Err(refusal),
}
};
w.commit().map_err(|e| be(&e))?;
Ok(decided)
})
.await
.map_err(|e| backend(&e))?;
outcome.map(|()| digest)
}
async fn row(
&self,
name: &str,
version: &str,
) -> Result<(String, Option<Attestation>), RegistryError> {
let tenant = self.tenant_name();
let (name, version) = (name.to_owned(), version.to_owned());
let (asked_name, asked_version) = (name.clone(), version.clone());
let found = self
.with_db(move |db| {
let r = db.begin_read().map_err(|e| be(&e))?;
let Ok(table) = r.open_table(MANIFESTS) else {
return Ok(None);
};
let Some(row) = table
.get((tenant.as_str(), name.as_str(), version.as_str()))
.map_err(|e| be(&e))?
else {
return Ok(None);
};
let (_, yaml, key_id, signature) = row.value();
Ok(Some((
yaml.to_owned(),
stored_attestation(key_id, signature),
)))
})
.await
.map_err(|e| backend(&e))?;
found.ok_or(RegistryError::NotFound {
name: asked_name,
version: asked_version,
})
}
}
#[async_trait]
impl Registry for RedbStore {
async fn publish(&self, manifest: &Manifest) -> Result<Digest, RegistryError> {
self.publish_row(manifest, None).await
}
async fn publish_signed(
&self,
manifest: &Manifest,
signer: &dyn Signer,
) -> Result<Digest, RegistryError> {
let (_, attestation) = attest_manifest(manifest, signer)?;
self.publish_row(manifest, Some(attestation)).await
}
async fn resolve(&self, name: &str, version: &str) -> Result<Manifest, RegistryError> {
let (yaml, _) = self.row(name, version).await?;
reparse(name, version, &yaml)
}
async fn resolve_verified(
&self,
name: &str,
version: &str,
verifier: &dyn Verifier,
) -> Result<(Manifest, KeyId), RegistryError> {
let (yaml, attestation) = self.row(name, version).await?;
let manifest = reparse(name, version, &yaml)?;
let key = check_attestation(name, version, &manifest, attestation.as_ref(), verifier)?;
Ok((manifest, key))
}
async fn versions(&self, name: &str) -> Result<Vec<String>, RegistryError> {
let tenant = self.tenant_name();
let name = name.to_owned();
self.with_db(move |db| {
let r = db.begin_read().map_err(|e| be(&e))?;
let Ok(table) = r.open_table(MANIFESTS) else {
return Ok(Vec::new());
};
let mut out = Vec::new();
for entry in table
.range(
(tenant.as_str(), name.as_str(), "")
..=(tenant.as_str(), name.as_str(), MAX_STR),
)
.map_err(|e| be(&e))?
{
let (key, _) = entry.map_err(|e| be(&e))?;
out.push(key.value().2.to_owned());
}
Ok(out)
})
.await
.map_err(|e| backend(&e))
}
async fn names(&self) -> Result<Vec<String>, RegistryError> {
let tenant = self.tenant_name();
self.with_db(move |db| {
let r = db.begin_read().map_err(|e| be(&e))?;
let Ok(table) = r.open_table(MANIFESTS) else {
return Ok(Vec::new());
};
let mut out: Vec<String> = Vec::new();
for entry in table
.range((tenant.as_str(), "", "")..=(tenant.as_str(), MAX_STR, MAX_STR))
.map_err(|e| be(&e))?
{
let (key, _) = entry.map_err(|e| be(&e))?;
let name = key.value().1.to_owned();
if out.last() != Some(&name) {
out.push(name);
}
}
Ok(out)
})
.await
.map_err(|e| backend(&e))
}
}