mod cmd;
mod errors;
mod query;
mod register;
mod spentbook;
pub use self::{
cmd::DataCmd,
errors::{Error, Result},
query::{DataQuery, DataQueryVariant},
register::{
CreateRegister, EditRegister, RegisterCmd, RegisterQuery, SignedRegisterCreate,
SignedRegisterEdit,
},
spentbook::{SpentbookCmd, SpentbookQuery},
};
use crate::network_knowledge::SectionTreeUpdate;
use crate::types::{
register::{Entry, EntryHash, Permissions, Policy, Register, User},
Chunk,
};
use crate::{messaging::MsgId, types::ReplicatedData};
use qp2p::UsrMsgBytes;
use serde::{Deserialize, Serialize};
use sn_dbc::SpentProofShare;
use std::{
collections::BTreeSet,
convert::TryFrom,
fmt::{self, Debug, Display, Formatter},
};
#[allow(clippy::large_enum_variant)]
#[derive(Debug, Eq, PartialEq, Clone, Serialize, Deserialize)]
pub enum ClientMsg {
Cmd(DataCmd),
Query(DataQuery),
}
impl Display for ClientMsg {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
match self {
Self::Cmd(cmd) => write!(f, "ClientMsg::Cmd({cmd:?})"),
Self::Query(query) => write!(f, "ClientMsg::Query({query:?})"),
}
}
}
#[allow(clippy::large_enum_variant)]
#[derive(Eq, PartialEq, Clone, Serialize, Deserialize, custom_debug::Debug)]
pub enum ClientDataResponse {
QueryResponse {
response: QueryResponse,
correlation_id: MsgId,
},
CmdResponse {
response: CmdResponse,
correlation_id: MsgId,
},
AntiEntropy {
section_tree_update: SectionTreeUpdate,
#[debug(skip)]
bounced_msg: UsrMsgBytes,
},
}
impl Display for ClientDataResponse {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
match self {
Self::QueryResponse { response, .. } => {
write!(f, "ClientDataResponse::QueryResponse({response:?})")
}
Self::CmdResponse { response, .. } => {
write!(f, "ClientDataResponse::CmdResponse({response:?})")
}
Self::AntiEntropy { .. } => {
write!(f, "ClientDataResponse::AntiEntropy")
}
}
}
}
#[allow(clippy::large_enum_variant, clippy::type_complexity)]
#[derive(Eq, PartialEq, Clone, Serialize, Deserialize, Debug)]
pub enum QueryResponse {
GetChunk(Result<Chunk>),
GetRegister(Result<Register>),
GetRegisterEntry(Result<Entry>),
GetRegisterOwner(Result<User>),
ReadRegister(Result<BTreeSet<(EntryHash, Entry)>>),
GetRegisterPolicy(Result<Policy>),
GetRegisterUserPermissions(Result<Permissions>),
SpentProofShares(Result<Vec<SpentProofShare>>),
}
impl QueryResponse {
pub fn is_success(&self) -> bool {
use QueryResponse::*;
matches!(
self,
GetChunk(Ok(_))
| GetRegister(Ok(_))
| GetRegisterEntry(Ok(_))
| GetRegisterOwner(Ok(_))
| ReadRegister(Ok(_))
| GetRegisterPolicy(Ok(_))
| GetRegisterUserPermissions(Ok(_))
| SpentProofShares(Ok(_))
)
}
pub fn is_data_not_found(&self) -> bool {
use QueryResponse::*;
matches!(
self,
GetChunk(Err(Error::DataNotFound(_)))
| GetRegister(Err(Error::DataNotFound(_)))
| GetRegisterEntry(Err(Error::DataNotFound(_)))
| GetRegisterEntry(Err(Error::NoSuchEntry(_)))
| GetRegisterOwner(Err(Error::DataNotFound(_)))
| GetRegisterOwner(Err(Error::NoSuchUser(_)))
| ReadRegister(Err(Error::DataNotFound(_)))
| GetRegisterPolicy(Err(Error::DataNotFound(_)))
| GetRegisterPolicy(Err(Error::NoSuchUser(_)))
| GetRegisterUserPermissions(Err(Error::DataNotFound(_)))
| GetRegisterUserPermissions(Err(Error::NoSuchUser(_)))
| SpentProofShares(Err(Error::DataNotFound(_)))
)
}
}
#[allow(clippy::large_enum_variant, clippy::type_complexity)]
#[derive(Eq, PartialEq, Clone, Serialize, Deserialize, Debug)]
pub enum CmdResponse {
StoreChunk(Result<()>),
CreateRegister(Result<()>),
EditRegister(Result<()>),
SpendKey(Result<()>),
}
impl CmdResponse {
#[allow(clippy::result_large_err)]
pub fn ok(data: ReplicatedData) -> Result<CmdResponse> {
let res = match &data {
ReplicatedData::Chunk(_) => CmdResponse::StoreChunk(Ok(())),
ReplicatedData::RegisterWrite(RegisterCmd::Create { .. }) => {
CmdResponse::CreateRegister(Ok(()))
}
ReplicatedData::RegisterWrite(RegisterCmd::Edit { .. }) => {
CmdResponse::EditRegister(Ok(()))
}
ReplicatedData::SpentbookWrite(_) => CmdResponse::SpendKey(Ok(())),
ReplicatedData::RegisterLog(_) => return Err(Error::NoCorrespondingCmdError), ReplicatedData::SpentbookLog(_) => return Err(Error::NoCorrespondingCmdError), };
Ok(res)
}
#[allow(clippy::result_large_err)]
pub fn err(data: ReplicatedData, err: Error) -> Result<CmdResponse> {
let res = match &data {
ReplicatedData::Chunk(_) => CmdResponse::StoreChunk(Err(err)),
ReplicatedData::RegisterWrite(RegisterCmd::Create { .. }) => {
CmdResponse::CreateRegister(Err(err))
}
ReplicatedData::RegisterWrite(RegisterCmd::Edit { .. }) => {
CmdResponse::EditRegister(Err(err))
}
ReplicatedData::SpentbookWrite(_) => CmdResponse::SpendKey(Err(err)),
ReplicatedData::RegisterLog(_) => return Err(Error::NoCorrespondingCmdError), ReplicatedData::SpentbookLog(_) => return Err(Error::NoCorrespondingCmdError), };
Ok(res)
}
pub fn is_success(&self) -> bool {
self.result().is_ok()
}
pub fn result(&self) -> &Result<()> {
use CmdResponse::*;
match self {
StoreChunk(result)
| CreateRegister(result)
| EditRegister(result)
| SpendKey(result) => result,
}
}
}
#[derive(Debug, Eq, PartialEq)]
#[allow(clippy::large_enum_variant)]
pub enum TryFromError {
WrongType,
Response(Error),
}
macro_rules! try_from {
($ok_type:ty, $($variant:ident),*) => {
impl TryFrom<QueryResponse> for $ok_type {
type Error = TryFromError;
fn try_from(response: QueryResponse) -> std::result::Result<Self, Self::Error> {
match response {
$(
QueryResponse::$variant(Ok(data)) => Ok(data),
QueryResponse::$variant(Err(error)) => Err(TryFromError::Response(error)),
)*
_ => Err(TryFromError::WrongType),
}
}
}
};
}
impl TryFrom<QueryResponse> for Chunk {
type Error = TryFromError;
fn try_from(response: QueryResponse) -> std::result::Result<Self, Self::Error> {
match response {
QueryResponse::GetChunk(Ok(data)) => Ok(data),
QueryResponse::GetChunk(Err(error)) => Err(TryFromError::Response(error)),
_ => Err(TryFromError::WrongType),
}
}
}
try_from!(Register, GetRegister);
try_from!(User, GetRegisterOwner);
try_from!(BTreeSet<(EntryHash, Entry)>, ReadRegister);
try_from!(Policy, GetRegisterPolicy);
try_from!(Permissions, GetRegisterUserPermissions);
#[cfg(test)]
mod tests {
use super::*;
use crate::types::{utils::random_bytes, Chunk, Keypair, PublicKey};
use bytes::Bytes;
use eyre::{eyre, Result};
use std::convert::{TryFrom, TryInto};
fn gen_keypairs() -> Vec<Keypair> {
let mut rng = rand::thread_rng();
let bls_secret_key = bls::SecretKeySet::random(1, &mut rng);
vec![
Keypair::new_ed25519(),
Keypair::new_bls_share(
0,
bls_secret_key.secret_key_share(0),
bls_secret_key.public_keys(),
),
]
}
fn gen_keys() -> Vec<PublicKey> {
gen_keypairs().iter().map(PublicKey::from).collect()
}
#[test]
fn debug_format_functional() -> Result<()> {
if let Some(key) = gen_keys().first() {
let errored_response =
QueryResponse::GetRegister(Err(Error::AccessDenied(User::Key(*key))));
assert!(format!("{errored_response:?}").contains("GetRegister(Err(AccessDenied("));
Ok(())
} else {
Err(eyre!("Could not generate public key"))
}
}
#[test]
fn try_from() -> Result<()> {
use QueryResponse::*;
let key = match gen_keys().first() {
Some(key) => User::Key(*key),
None => return Err(eyre!("Could not generate public key")),
};
let i_data = Chunk::new(Bytes::from(vec![1, 3, 1, 4]));
let e = Error::AccessDenied(key);
assert_eq!(
i_data,
GetChunk(Ok(i_data.clone()))
.try_into()
.map_err(|_| eyre!("Mismatched types".to_string()))?
);
assert_eq!(
Err(TryFromError::Response(e.clone())),
Chunk::try_from(GetChunk(Err(e)))
);
Ok(())
}
#[test]
fn wire_msg_payload() -> Result<()> {
use crate::messaging::data::ClientMsg;
use crate::messaging::data::DataCmd;
use crate::messaging::WireMsg;
let chunks = (0..10).map(|_| Chunk::new(random_bytes(3072)));
for chunk in chunks {
let (original_msg, serialised_cmd) = {
let msg = ClientMsg::Cmd(DataCmd::StoreChunk(chunk));
let bytes = WireMsg::serialize_msg_payload(&msg)?;
(msg, bytes)
};
let deserialized_msg: ClientMsg =
rmp_serde::from_slice(&serialised_cmd).map_err(|err| {
crate::messaging::Error::FailedToParse(format!(
"Data message payload as Msgpack: {err}",
))
})?;
assert_eq!(original_msg, deserialized_msg);
}
Ok(())
}
}