use super::{DataIdentifier, NO_OWNER_PUB_KEY};
use error::RoutingError;
use maidsafe_utilities::serialisation::{serialise, serialised_size};
use rust_sodium::crypto::sign::{self, PublicKey, SecretKey, Signature};
use std::collections::{BTreeMap, BTreeSet};
use std::fmt::{self, Debug, Formatter};
use utils;
use xor_name::XorName;
pub const MAX_STRUCTURED_DATA_SIZE_IN_BYTES: u64 = 102400;
#[derive(Hash, Eq, PartialEq, PartialOrd, Ord, Clone, RustcDecodable, RustcEncodable)]
pub struct StructuredData {
type_tag: u64,
name: XorName,
data: Vec<u8>,
version: u64,
owners: BTreeSet<PublicKey>,
signatures: BTreeMap<PublicKey, Signature>,
}
impl StructuredData {
pub fn new(type_tag: u64,
name: XorName,
version: u64,
data: Vec<u8>,
owners: BTreeSet<PublicKey>)
-> Result<StructuredData, RoutingError> {
if owners.len() > 1 {
return Err(RoutingError::InvalidOwners);
}
Ok(StructuredData {
type_tag: type_tag,
name: name,
data: data,
version: version,
owners: owners,
signatures: BTreeMap::new(),
})
}
pub fn replace_with_other(&mut self, other: StructuredData) -> Result<(), RoutingError> {
self.validate_self_against_successor(&other)?;
self.type_tag = other.type_tag;
self.name = other.name;
self.data = other.data;
self.version = other.version;
self.owners = other.owners;
self.signatures = other.signatures;
Ok(())
}
pub fn name(&self) -> &XorName {
&self.name
}
pub fn identifier(&self) -> DataIdentifier {
DataIdentifier::Structured(self.name, self.type_tag)
}
pub fn delete_if_valid_successor(&mut self,
other: &StructuredData)
-> Result<(), RoutingError> {
self.validate_self_against_successor(other)?;
self.data.clear();
self.version += 1;
self.owners.clear();
self.signatures.clear();
Ok(())
}
pub fn is_deleted(&self) -> bool {
self.data.is_empty() && self.owners.is_empty() && self.signatures.is_empty()
}
pub fn validate_self_against_successor(&self,
other: &StructuredData)
-> Result<(), RoutingError> {
if other.owners.len() > 1 || other.signatures.len() > 1 ||
self.owners.contains(&NO_OWNER_PUB_KEY) {
return Err(RoutingError::InvalidOwners);
}
if other.type_tag != self.type_tag || other.name != self.name ||
other.version != self.version + 1 {
return Err(RoutingError::UnknownMessageType);
}
let data = other.data_to_sign()?;
super::verify_signatures(&self.owners, &data, &other.signatures)
}
fn data_to_sign(&self) -> Result<Vec<u8>, RoutingError> {
let sd = SerialisableStructuredData {
type_tag: self.type_tag
.to_string()
.as_bytes()
.to_vec(),
name: self.name,
data: &self.data,
version: self.version
.to_string()
.as_bytes()
.to_vec(),
owners: &self.owners,
};
serialise(&sd).map_err(From::from)
}
pub fn add_signature(&mut self, keys: &(PublicKey, SecretKey)) -> Result<usize, RoutingError> {
if !self.signatures.is_empty() {
return Err(RoutingError::InvalidOwners);
}
let data = self.data_to_sign()?;
let sig = sign::sign_detached(&data, &keys.1);
if self.signatures.insert(keys.0, sig).is_none() {
return Ok(((self.owners.len() / 2) + 1).saturating_sub(self.signatures.len()));
}
Err(RoutingError::FailedSignature)
}
pub fn replace_signatures(&mut self, new_signatures: BTreeMap<PublicKey, Signature>) {
self.signatures = new_signatures;
}
pub fn get_type_tag(&self) -> u64 {
self.type_tag
}
pub fn get_data(&self) -> &Vec<u8> {
&self.data
}
pub fn get_version(&self) -> u64 {
self.version
}
pub fn get_owners(&self) -> &BTreeSet<PublicKey> {
&self.owners
}
pub fn get_signatures(&self) -> &BTreeMap<PublicKey, Signature> {
&self.signatures
}
pub fn validate_size(&self) -> bool {
serialised_size(self) <= MAX_STRUCTURED_DATA_SIZE_IN_BYTES
}
}
impl Debug for StructuredData {
fn fmt(&self, formatter: &mut Formatter) -> fmt::Result {
write!(formatter,
"StructuredData {{ type_tag: {}, name: {}, version: {}, data: {}, \
owners: {:?}, signatures: {:?} }}",
self.type_tag,
self.name(),
self.version,
utils::format_binary_array(&self.data[..]),
self.owners,
self.signatures)
}
}
#[derive(RustcEncodable)]
struct SerialisableStructuredData<'a> {
type_tag: Vec<u8>,
name: XorName,
data: &'a [u8],
version: Vec<u8>,
owners: &'a BTreeSet<PublicKey>,
}
#[cfg(test)]
mod tests {
use super::*;
use data;
use rand;
use rust_sodium::crypto::sign;
use std::collections::BTreeSet;
use xor_name::XorName;
#[test]
fn single_owner() {
let keys = sign::gen_keypair();
let mut owner_keys = BTreeSet::new();
owner_keys.insert(keys.0);
match StructuredData::new(0, rand::random(), 0, vec![], owner_keys.clone()) {
Ok(mut structured_data) => {
let data = match structured_data.data_to_sign() {
Ok(data) => data,
Err(error) => panic!("Error: {:?}", error),
};
assert!(data::verify_signatures(&owner_keys,
&data,
structured_data.get_signatures())
.is_err());
assert!(structured_data.add_signature(&keys).is_ok());
assert!(data::verify_signatures(&owner_keys,
&data,
structured_data.get_signatures())
.is_ok());
}
Err(error) => panic!("Error: {:?}", error),
}
}
#[test]
fn single_owner_other_signature() {
let keys = sign::gen_keypair();
let other_keys = sign::gen_keypair();
let mut owner_keys = BTreeSet::new();
owner_keys.insert(keys.0);
match StructuredData::new(0, rand::random(), 0, vec![], owner_keys.clone()) {
Ok(mut structured_data) => {
assert!(structured_data.add_signature(&other_keys).is_ok());
let data = match structured_data.data_to_sign() {
Ok(data) => data,
Err(error) => panic!("Error: {:?}", error),
};
assert!(data::verify_signatures(&owner_keys,
&data,
structured_data.get_signatures())
.is_err());
}
Err(error) => panic!("Error: {:?}", error),
}
}
#[test]
fn transfer_ownership() {
let keys = sign::gen_keypair();
let other_keys = sign::gen_keypair();
let mut owner = BTreeSet::new();
owner.insert(keys.0);
let mut new_owner = BTreeSet::new();
new_owner.insert(other_keys.0);
let name: XorName = rand::random();
let mut sd = unwrap!(StructuredData::new(0, name, 0, vec![], owner));
let mut sd_new = unwrap!(StructuredData::new(0, name, 1, vec![], new_owner.clone()));
assert!(sd_new.add_signature(&keys).is_ok());
assert!(sd.replace_with_other(sd_new).is_ok());
let mut sd_fail = unwrap!(StructuredData::new(0, name, 2, vec![], new_owner));
assert!(sd_fail.add_signature(&keys).is_ok());
assert!(sd.replace_with_other(sd_fail).is_err());
}
}