use crate::tickets::UnacknowledgedTicket;
use bloomfilter::Bloom;
use ethers::utils::hex;
use hopr_crypto_random::random_bytes;
use hopr_crypto_types::prelude::*;
use hopr_primitive_types::prelude::*;
use serde::{Deserialize, Serialize};
use std::fmt::{Display, Formatter};
use tracing::warn;
use crate::errors::{CoreTypesError, CoreTypesError::PayloadSizeExceeded, Result};
pub const INTERMEDIATE_HOPS: usize = 3;
pub const PAYLOAD_SIZE: usize = 496;
pub const DEFAULT_MINIMUM_INCOMING_TICKET_WIN_PROB: f64 = 1.0;
pub const DEFAULT_MAXIMUM_INCOMING_TICKET_WIN_PROB: f64 = 1.0; pub const DEFAULT_OUTGOING_TICKET_WIN_PROB: f64 = 1.0;
pub const LOWEST_POSSIBLE_WINNING_PROB: f64 = 0.00000001;
pub type Tag = u16;
pub const DEFAULT_APPLICATION_TAG: Tag = 0;
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
pub struct Acknowledgement {
ack_signature: OffchainSignature,
pub ack_key_share: HalfKey,
validated: bool,
}
impl Acknowledgement {
pub fn new(ack_key_share: HalfKey, node_keypair: &OffchainKeypair) -> Self {
Self {
ack_signature: OffchainSignature::sign_message(ack_key_share.as_ref(), node_keypair),
ack_key_share,
validated: true,
}
}
pub fn random(offchain_keypair: &OffchainKeypair) -> Self {
Self::new(HalfKey::random(), offchain_keypair)
}
#[tracing::instrument(level = "debug", skip(self, sender_node_key))]
pub fn validate(&mut self, sender_node_key: &OffchainPublicKey) -> bool {
self.validated = self
.ack_signature
.verify_message(self.ack_key_share.as_ref(), sender_node_key);
self.validated
}
pub fn ack_challenge(&self) -> HalfKeyChallenge {
assert!(self.validated, "acknowledgement not validated");
self.ack_key_share.to_challenge()
}
}
#[allow(clippy::large_enum_variant)]
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub enum PendingAcknowledgement {
WaitingAsSender,
WaitingAsRelayer(UnacknowledgedTicket),
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct TagBloomFilter {
bloom: Bloom<PacketTag>,
count: usize,
capacity: usize,
}
impl TagBloomFilter {
const FALSE_POSITIVE_RATE: f64 = 0.00001_f64;
const DEFAULT_MAX_ITEMS: usize = 10_000_000;
pub fn count(&self) -> usize {
self.count
}
pub fn capacity(&self) -> usize {
self.capacity
}
pub fn set(&mut self, tag: &PacketTag) {
if self.count == self.capacity {
warn!("maximum number of items in the Bloom filter reached!");
self.bloom.clear();
self.count = 0;
}
self.bloom.set(tag);
self.count += 1;
}
pub fn check(&self, tag: &PacketTag) -> bool {
self.bloom.check(tag)
}
pub fn check_and_set(&mut self, tag: &PacketTag) -> bool {
if self.count == self.capacity {
let is_present = self.bloom.check(tag);
if !is_present {
warn!("maximum number of items in the Bloom filter reached!");
self.bloom.clear();
self.bloom.set(tag);
self.count = 1;
}
is_present
} else {
let was_present = self.bloom.check_and_set(tag);
if !was_present {
self.count += 1;
}
was_present
}
}
pub fn from_bytes(data: &[u8]) -> Result<Self> {
bincode::deserialize(data).map_err(|e| CoreTypesError::ParseError(e.to_string()))
}
pub fn to_bytes(&self) -> Box<[u8]> {
bincode::serialize(&self)
.expect("serialization must not fail")
.into_boxed_slice()
}
fn with_capacity(size: usize) -> Self {
Self {
bloom: Bloom::new_for_fp_rate_with_seed(size, Self::FALSE_POSITIVE_RATE, &random_bytes()),
count: 0,
capacity: size,
}
}
}
impl Default for TagBloomFilter {
fn default() -> Self {
Self::with_capacity(Self::DEFAULT_MAX_ITEMS)
}
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct ApplicationData {
pub application_tag: Option<Tag>,
#[serde(with = "serde_bytes")]
pub plain_text: Box<[u8]>,
}
impl ApplicationData {
pub fn new(application_tag: Option<Tag>, plain_text: &[u8]) -> Result<Self> {
if plain_text.len() <= PAYLOAD_SIZE - Self::SIZE {
Ok(Self {
application_tag,
plain_text: plain_text.into(),
})
} else {
Err(PayloadSizeExceeded)
}
}
pub fn new_from_owned(application_tag: Option<Tag>, plain_text: Box<[u8]>) -> Result<Self> {
if plain_text.len() <= PAYLOAD_SIZE - Self::SIZE {
Ok(Self {
application_tag,
plain_text,
})
} else {
Err(PayloadSizeExceeded)
}
}
}
impl Display for ApplicationData {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
write!(
f,
"({}): {}",
self.application_tag.unwrap_or(DEFAULT_APPLICATION_TAG),
hex::encode(&self.plain_text)
)
}
}
impl ApplicationData {
const SIZE: usize = 2; pub fn from_bytes(data: &[u8]) -> hopr_primitive_types::errors::Result<Self> {
if data.len() <= PAYLOAD_SIZE && data.len() >= Self::SIZE {
let mut tag = [0u8; 2];
tag.copy_from_slice(&data[0..2]);
let tag = u16::from_be_bytes(tag);
Ok(Self {
application_tag: if tag != DEFAULT_APPLICATION_TAG {
Some(tag)
} else {
None
},
plain_text: (&data[2..]).into(),
})
} else {
Err(GeneralError::ParseError("ApplicationData".into()))
}
}
pub fn to_bytes(&self) -> Box<[u8]> {
let mut buf = Vec::with_capacity(Self::SIZE + self.plain_text.len());
let tag = self.application_tag.unwrap_or(DEFAULT_APPLICATION_TAG);
buf.extend_from_slice(&tag.to_be_bytes());
buf.extend_from_slice(&self.plain_text);
buf.into_boxed_slice()
}
}
#[cfg(test)]
mod tests {
use super::*;
use hopr_crypto_random::random_bytes;
use hex_literal::hex;
const PRIVATE_KEY: [u8; 32] = hex!("51d3003d908045a4d76d0bfc0d84f6ff946b5934b7ea6a2958faf02fead4567a");
#[test]
fn acknowledgement_binary_compatibility_with_the_v2_format() -> anyhow::Result<()> {
let offchain_kp = OffchainKeypair::from_secret(&PRIVATE_KEY)?;
let mut ack = Acknowledgement::new(HalfKey::default(), &offchain_kp);
assert!(ack.validate(&offchain_kp.public()));
let buf = Vec::new();
let serialized = cbor4ii::serde::to_vec(buf, &ack)?;
const EXPECTED_V2_BINARY_REPRESENTATION_CBOR_HEX: [u8; 213] = hex!("a36d61636b5f7369676e6174757265a1697369676e617475726598401859182418be184818a218c318cb1869186018270218391853186c18ff18e018b518d9187b187900188218da184e1869187518ec1828181b081821187718bb0c18ba18f418331218ea187c1880182318d6189f189f18d7141876186a1890186b1885189718a718b9189018fc18bc18260918e318a5182a006d61636b5f6b65795f7368617265a164686b6579982000000000000000000000000000000000000000000000000000000000000000016976616c696461746564f5");
assert_eq!(&serialized, &EXPECTED_V2_BINARY_REPRESENTATION_CBOR_HEX);
Ok(())
}
#[test]
fn test_application_data() -> anyhow::Result<()> {
let ad_1 = ApplicationData::new(Some(10), &[0_u8, 1_u8])?;
let ad_2 = ApplicationData::from_bytes(&ad_1.to_bytes())?;
assert_eq!(ad_1, ad_2);
let ad_1 = ApplicationData::new(None, &[])?;
let ad_2 = ApplicationData::from_bytes(&ad_1.to_bytes())?;
assert_eq!(ad_1, ad_2);
let ad_1 = ApplicationData::new(Some(10), &[0_u8, 1_u8])?;
let ad_2 = ApplicationData::from_bytes(&ad_1.to_bytes())?;
assert_eq!(ad_1, ad_2);
Ok(())
}
const ZEROS_TAG: [u8; PACKET_TAG_LENGTH] = [0; PACKET_TAG_LENGTH];
const ONES_TAG: [u8; PACKET_TAG_LENGTH] = [1; PACKET_TAG_LENGTH];
#[test]
fn test_packet_tag_bloom_filter() -> anyhow::Result<()> {
let mut filter1 = TagBloomFilter::default();
let items = (0..10_000)
.map(|i| {
let mut ret = random_bytes::<{ hopr_crypto_types::types::PACKET_TAG_LENGTH }>();
ret[i % hopr_crypto_types::types::PACKET_TAG_LENGTH] = 0xaa; ret
})
.collect::<Vec<_>>();
items.iter().for_each(|item| filter1.set(item));
assert_eq!(items.len(), filter1.count(), "invalid number of items in bf");
let match_count_1 = items.iter().filter(|item| filter1.check(item)).count();
let filter2 = TagBloomFilter::from_bytes(&filter1.to_bytes())?;
let match_count_2 = items.iter().filter(|item| filter2.check(item)).count();
assert_eq!(
match_count_1, match_count_2,
"the number of false positives must be equal"
);
assert_eq!(filter1.count(), filter2.count(), "the number of items must be equal");
assert!(!filter1.check(&ZEROS_TAG), "bf 1 must not contain zero tag");
assert!(!filter2.check(&ZEROS_TAG), "bf 2 must not contain zero tag");
Ok(())
}
#[test]
fn tag_bloom_filter_count() {
let mut filter = TagBloomFilter::default();
assert!(!filter.check_and_set(&ZEROS_TAG));
assert_eq!(1, filter.count());
assert!(filter.check_and_set(&ZEROS_TAG));
assert_eq!(1, filter.count());
assert!(!filter.check_and_set(&ONES_TAG));
assert_eq!(2, filter.count());
assert!(filter.check_and_set(&ZEROS_TAG));
assert_eq!(2, filter.count());
}
#[test]
fn tag_bloom_filter_wrap_around() {
let mut filter = TagBloomFilter::with_capacity(1000);
for _ in 1..filter.capacity() {
let mut tag: PacketTag = hopr_crypto_random::random_bytes();
tag[0] = 0xaa; assert!(!filter.check_and_set(&tag));
}
assert_eq!(filter.capacity() - 1, filter.count());
assert!(!filter.check_and_set(&ZEROS_TAG));
assert_eq!(filter.capacity(), filter.count());
assert!(filter.check(&ZEROS_TAG));
assert!(filter.check_and_set(&ZEROS_TAG));
assert_eq!(filter.capacity(), filter.count());
assert!(!filter.check_and_set(&ONES_TAG));
assert_eq!(1, filter.count());
assert!(filter.check(&ONES_TAG));
}
}