use crate::header::WireDelete;
use crate::header::WireHeaderStatus;
use crate::header::WireNewEntryHeader;
use crate::header::WireUpdateRelationship;
use crate::prelude::*;
use error::ElementGroupError;
use error::ElementGroupResult;
use holochain_keystore::KeystoreError;
use holochain_keystore::LairResult;
use holochain_keystore::MetaLairClient;
use holochain_serialized_bytes::prelude::*;
use holochain_zome_types::entry::EntryHashed;
use std::borrow::Cow;
use std::collections::BTreeSet;
#[allow(missing_docs)]
pub mod error;
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, SerializedBytes, Default)]
pub struct WireElementOps {
pub header: Option<Judged<SignedHeader>>,
pub deletes: Vec<Judged<WireDelete>>,
pub updates: Vec<Judged<WireUpdateRelationship>>,
pub entry: Option<Entry>,
}
impl WireElementOps {
pub fn new() -> Self {
Self::default()
}
pub fn render(self) -> DhtOpResult<RenderedOps> {
let Self {
header,
deletes,
updates,
entry,
} = self;
let mut ops = Vec::with_capacity(1 + deletes.len() + updates.len());
if let Some(header) = header {
let status = header.validation_status();
let SignedHeader(header, signature) = header.data;
let entry_hash = header.entry_hash().cloned();
ops.push(RenderedOp::new(
header,
signature,
status,
DhtOpType::StoreElement,
)?);
if let Some(entry_hash) = entry_hash {
for op in deletes {
let status = op.validation_status();
let op = op.data;
let signature = op.signature;
let header = Header::Delete(op.delete);
ops.push(RenderedOp::new(
header,
signature,
status,
DhtOpType::RegisterDeletedBy,
)?);
}
for op in updates {
let status = op.validation_status();
let SignedHeader(header, signature) =
op.data.into_signed_header(entry_hash.clone());
ops.push(RenderedOp::new(
header,
signature,
status,
DhtOpType::RegisterUpdatedElement,
)?);
}
}
}
Ok(RenderedOps {
entry: entry.map(EntryHashed::from_content_sync),
ops,
})
}
}
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize, SerializedBytes)]
pub struct WireElement {
signed_header: SignedHeader,
maybe_entry: Option<Entry>,
validation_status: ValidationStatus,
deletes: Vec<WireHeaderStatus<WireDelete>>,
updates: Vec<WireHeaderStatus<WireUpdateRelationship>>,
}
#[derive(Debug, Clone)]
pub struct ElementGroup<'a> {
headers: Vec<Cow<'a, SignedHeaderHashed>>,
rejected: Vec<Cow<'a, SignedHeaderHashed>>,
entry: Cow<'a, EntryHashed>,
}
#[derive(Debug, Clone, derive_more::Constructor)]
pub struct ElementStatus {
pub element: Element,
pub status: ValidationStatus,
}
impl<'a> ElementGroup<'a> {
pub fn headers_and_hashes(&self) -> impl Iterator<Item = (&HeaderHash, &Header)> {
self.headers
.iter()
.map(|shh| shh.header_address())
.zip(self.headers.iter().map(|shh| shh.header()))
}
pub fn is_empty(&self) -> bool {
self.len() == 0
}
pub fn len(&self) -> usize {
self.headers.len()
}
pub fn visibility(&self) -> ElementGroupResult<&EntryVisibility> {
self.headers
.first()
.ok_or(ElementGroupError::Empty)?
.header()
.entry_data()
.map(|(_, et)| et.visibility())
.ok_or(ElementGroupError::MissingEntryData)
}
pub fn entry_hash(&self) -> &EntryHash {
self.entry.as_hash()
}
pub fn entry_hashed(&self) -> EntryHashed {
self.entry.clone().into_owned()
}
pub fn owned_signed_headers(&self) -> impl Iterator<Item = SignedHeaderHashed> + 'a {
self.headers
.clone()
.into_iter()
.chain(self.rejected.clone().into_iter())
.map(|shh| shh.into_owned())
}
pub fn valid_hashes(&self) -> impl Iterator<Item = &HeaderHash> {
self.headers.iter().map(|shh| shh.header_address())
}
pub fn rejected_hashes(&self) -> impl Iterator<Item = &HeaderHash> {
self.rejected.iter().map(|shh| shh.header_address())
}
pub fn from_wire_elements<I: IntoIterator<Item = WireHeaderStatus<WireNewEntryHeader>>>(
headers_iter: I,
entry_type: EntryType,
entry: Entry,
) -> ElementGroupResult<ElementGroup<'a>> {
let iter = headers_iter.into_iter();
let mut valid = Vec::with_capacity(iter.size_hint().0);
let mut rejected = Vec::with_capacity(iter.size_hint().0);
let entry = entry.into_hashed();
let entry_hash = entry.as_hash().clone();
let entry = Cow::Owned(entry);
for wire in iter {
match wire.validation_status {
ValidationStatus::Valid => valid.push(Cow::Owned(
wire.header
.into_header(entry_type.clone(), entry_hash.clone()),
)),
ValidationStatus::Rejected => rejected.push(Cow::Owned(
wire.header
.into_header(entry_type.clone(), entry_hash.clone()),
)),
ValidationStatus::Abandoned => todo!(),
}
}
Ok(Self {
headers: valid,
rejected,
entry,
})
}
}
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize, SerializedBytes)]
pub enum GetElementResponse {
GetEntryFull(Option<Box<RawGetEntryResponse>>),
GetEntryPartial,
GetEntryCollapsed,
GetHeader(Option<Box<WireElement>>),
}
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize, SerializedBytes)]
pub struct RawGetEntryResponse {
pub live_headers: BTreeSet<WireHeaderStatus<WireNewEntryHeader>>,
pub deletes: Vec<WireHeaderStatus<WireDelete>>,
pub updates: Vec<WireHeaderStatus<WireUpdateRelationship>>,
pub entry: Entry,
pub entry_type: EntryType,
}
impl RawGetEntryResponse {
pub fn from_elements<E>(
elements: E,
deletes: Vec<WireHeaderStatus<WireDelete>>,
updates: Vec<WireHeaderStatus<WireUpdateRelationship>>,
) -> Option<Self>
where
E: IntoIterator<Item = ElementStatus>,
{
let mut elements = elements.into_iter();
elements.next().map(|ElementStatus { element, status }| {
let mut live_headers = BTreeSet::new();
let (new_entry_header, entry_type, entry) = Self::from_element(element);
live_headers.insert(WireHeaderStatus::new(new_entry_header, status));
let r = Self {
live_headers,
deletes,
updates,
entry,
entry_type,
};
elements.fold(r, |mut response, ElementStatus { element, status }| {
let (new_entry_header, entry_type, entry) = Self::from_element(element);
debug_assert_eq!(response.entry, entry);
debug_assert_eq!(response.entry_type, entry_type);
response
.live_headers
.insert(WireHeaderStatus::new(new_entry_header, status));
response
})
})
}
fn from_element(element: Element) -> (WireNewEntryHeader, EntryType, Entry) {
let (shh, entry) = element.into_inner();
let entry = entry
.into_option()
.expect("Get entry responses cannot be created without entries");
let (header, signature) = shh.into_header_and_signature();
let (new_entry_header, entry_type) = match header.into_content() {
Header::Create(ec) => {
let et = ec.entry_type.clone();
(WireNewEntryHeader::Create((ec, signature).into()), et)
}
Header::Update(eu) => {
let et = eu.entry_type.clone();
(WireNewEntryHeader::Update((eu, signature).into()), et)
}
h => panic!(
"Get entry responses cannot be created from headers
other then Create or Update.
Tried to with: {:?}",
h
),
};
(new_entry_header, entry_type, entry)
}
}
#[async_trait::async_trait]
pub trait ElementExt {
async fn validate(&self) -> Result<(), KeystoreError>;
}
#[async_trait::async_trait]
impl ElementExt for Element {
async fn validate(&self) -> Result<(), KeystoreError> {
self.signed_header().validate().await?;
Ok(())
}
}
#[async_trait::async_trait]
pub trait SignedHeaderHashedExt {
fn from_content_sync(signed_header: SignedHeader) -> SignedHeaderHashed;
#[allow(clippy::new_ret_no_self)]
async fn new(keystore: &MetaLairClient, header: HeaderHashed)
-> LairResult<SignedHeaderHashed>;
async fn validate(&self) -> Result<(), KeystoreError>;
}
#[allow(missing_docs)]
#[async_trait::async_trait]
impl SignedHeaderHashedExt for SignedHeaderHashed {
fn from_content_sync(signed_header: SignedHeader) -> Self
where
Self: Sized,
{
let (header, signature) = signed_header.into();
Self::with_presigned(header.into_hashed(), signature)
}
async fn new(keystore: &MetaLairClient, header: HeaderHashed) -> LairResult<Self> {
let signature = header.author().sign(keystore, &*header).await?;
Ok(Self::with_presigned(header, signature))
}
async fn validate(&self) -> Result<(), KeystoreError> {
if !self
.header()
.author()
.verify_signature(self.signature(), self.header())
.await
{
return Err(KeystoreError::InvalidSignature(
self.signature().clone(),
format!("header {:?}", self.header_address()),
));
}
Ok(())
}
}
impl WireElement {
pub fn into_parts(self) -> (ElementStatus, Vec<ElementStatus>, Vec<ElementStatus>) {
let entry_hash = self.signed_header.header().entry_hash().cloned();
let header = Element::new(
SignedHeaderHashed::from_content_sync(self.signed_header),
self.maybe_entry,
);
let deletes = self
.deletes
.into_iter()
.map(WireHeaderStatus::<WireDelete>::into_element_status)
.collect();
let updates = self
.updates
.into_iter()
.map(|u| {
let entry_hash = entry_hash
.clone()
.expect("Updates cannot be on headers that do not have entries");
u.into_element_status(entry_hash)
})
.collect();
(
ElementStatus::new(header, self.validation_status),
deletes,
updates,
)
}
pub fn from_element(
e: ElementStatus,
deletes: Vec<WireHeaderStatus<WireDelete>>,
updates: Vec<WireHeaderStatus<WireUpdateRelationship>>,
) -> Self {
let ElementStatus { element, status } = e;
let (signed_header, maybe_entry) = element.into_inner();
Self {
signed_header: signed_header.into_inner().0,
maybe_entry: maybe_entry.into_option(),
validation_status: status,
deletes,
updates,
}
}
pub fn entry_hash(&self) -> Option<&EntryHash> {
self.signed_header
.header()
.entry_data()
.map(|(hash, _)| hash)
}
}
#[cfg(test)]
mod tests {
use super::SignedHeader;
use super::SignedHeaderHashed;
use crate::prelude::*;
use ::fixt::prelude::*;
use holo_hash::HasHash;
use holo_hash::HoloHashed;
#[tokio::test(flavor = "multi_thread")]
async fn test_signed_header_roundtrip() {
let signature = SignatureFixturator::new(Unpredictable).next().unwrap();
let header = HeaderFixturator::new(Unpredictable).next().unwrap();
let signed_header = SignedHeader(header, signature);
let hashed: HoloHashed<SignedHeader> = HoloHashed::from_content_sync(signed_header);
let shh: SignedHeaderHashed = hashed.clone().into();
assert_eq!(shh.header_address(), hashed.as_hash());
let round: HoloHashed<SignedHeader> = shh.into();
assert_eq!(hashed, round);
}
}