mod error;
mod messages;
mod topics;
pub use error::FromStrError;
pub use messages::{Message, MessageChecksum, MessagePayload, Messages};
pub use topics::{
MessageTopicOperation, MessageTopicSummary, TopicNameHash, TOPIC_NAME_HASH_LENGTH,
};
use crate::{
bytesrepr::{self, FromBytes, ToBytes},
checksummed_hex, HashAddr,
};
use alloc::{string::String, vec::Vec};
use core::fmt::{Debug, Display, Formatter};
use crate::account::ACCOUNT_HASH_LENGTH;
#[cfg(feature = "datasize")]
use datasize::DataSize;
#[cfg(any(feature = "testing", test))]
use rand::{
distributions::{Distribution, Standard},
Rng,
};
#[cfg(feature = "json-schema")]
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
const TOPIC_FORMATTED_STRING_PREFIX: &str = "topic-";
const MESSAGE_ADDR_PREFIX: &str = "message-";
#[derive(PartialOrd, Ord, PartialEq, Eq, Hash, Clone, Copy, Serialize, Deserialize, Debug)]
#[cfg_attr(feature = "json-schema", derive(JsonSchema))]
#[cfg_attr(feature = "datasize", derive(DataSize))]
pub struct MessageAddr {
hash_addr: HashAddr,
topic_name_hash: TopicNameHash,
message_index: Option<u32>,
}
impl MessageAddr {
pub const fn new_topic_addr(hash_addr: HashAddr, topic_name_hash: TopicNameHash) -> Self {
Self {
hash_addr,
topic_name_hash,
message_index: None,
}
}
pub const fn new_message_addr(
hash_addr: HashAddr,
topic_name_hash: TopicNameHash,
message_index: u32,
) -> Self {
Self {
hash_addr,
topic_name_hash,
message_index: Some(message_index),
}
}
pub fn to_formatted_string(self) -> String {
match self.message_index {
Some(index) => {
format!(
"{}{}-{}-{:x}",
MESSAGE_ADDR_PREFIX,
base16::encode_lower(&self.hash_addr),
self.topic_name_hash.to_formatted_string(),
index,
)
}
None => {
format!(
"{}{}{}-{}",
MESSAGE_ADDR_PREFIX,
TOPIC_FORMATTED_STRING_PREFIX,
base16::encode_lower(&self.hash_addr),
self.topic_name_hash.to_formatted_string(),
)
}
}
}
pub fn from_formatted_str(input: &str) -> Result<Self, FromStrError> {
let remainder = input
.strip_prefix(MESSAGE_ADDR_PREFIX)
.ok_or(FromStrError::InvalidPrefix)?;
let (remainder, message_index) = match remainder.strip_prefix(TOPIC_FORMATTED_STRING_PREFIX)
{
Some(topic_string) => (topic_string, None),
None => {
let (remainder, message_index_str) = remainder
.rsplit_once('-')
.ok_or(FromStrError::MissingMessageIndex)?;
(remainder, Some(u32::from_str_radix(message_index_str, 16)?))
}
};
let (hash_addr_str, topic_name_hash_str) = remainder
.rsplit_once('-')
.ok_or(FromStrError::MissingMessageIndex)?;
let hash_addr = <[u8; ACCOUNT_HASH_LENGTH]>::try_from(
checksummed_hex::decode(hash_addr_str)?.as_ref(),
)?;
let topic_name_hash = TopicNameHash::from_formatted_str(topic_name_hash_str)?;
Ok(MessageAddr {
hash_addr,
topic_name_hash,
message_index,
})
}
pub fn hash_addr(&self) -> HashAddr {
self.hash_addr
}
pub fn topic_name_hash(&self) -> TopicNameHash {
self.topic_name_hash
}
pub fn message_index(&self) -> Option<u32> {
self.message_index
}
}
impl Display for MessageAddr {
fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
match self.message_index {
Some(index) => {
write!(
f,
"{}-{}-{:x}",
base16::encode_lower(&self.hash_addr),
self.topic_name_hash,
index,
)
}
None => {
write!(
f,
"{}-{}",
base16::encode_lower(&self.hash_addr),
self.topic_name_hash
)
}
}
}
}
impl ToBytes for MessageAddr {
fn to_bytes(&self) -> Result<Vec<u8>, bytesrepr::Error> {
let mut buffer = bytesrepr::allocate_buffer(self)?;
buffer.append(&mut self.hash_addr.to_bytes()?);
buffer.append(&mut self.topic_name_hash.to_bytes()?);
buffer.append(&mut self.message_index.to_bytes()?);
Ok(buffer)
}
fn serialized_length(&self) -> usize {
self.hash_addr.serialized_length()
+ self.topic_name_hash.serialized_length()
+ self.message_index.serialized_length()
}
}
impl FromBytes for MessageAddr {
fn from_bytes(bytes: &[u8]) -> Result<(Self, &[u8]), bytesrepr::Error> {
let (entity_addr, rem) = FromBytes::from_bytes(bytes)?;
let (topic_hash, rem) = FromBytes::from_bytes(rem)?;
let (message_index, rem) = FromBytes::from_bytes(rem)?;
Ok((
MessageAddr {
hash_addr: entity_addr,
topic_name_hash: topic_hash,
message_index,
},
rem,
))
}
}
#[cfg(any(feature = "testing", test))]
impl Distribution<MessageAddr> for Standard {
fn sample<R: Rng + ?Sized>(&self, rng: &mut R) -> MessageAddr {
MessageAddr {
hash_addr: rng.gen(),
topic_name_hash: rng.gen(),
message_index: rng.gen(),
}
}
}
#[cfg(test)]
mod tests {
use crate::{bytesrepr, KEY_HASH_LENGTH};
use super::{topics::TOPIC_NAME_HASH_LENGTH, *};
#[test]
fn serialization_roundtrip() {
let topic_addr =
MessageAddr::new_topic_addr([1; KEY_HASH_LENGTH], [2; TOPIC_NAME_HASH_LENGTH].into());
bytesrepr::test_serialization_roundtrip(&topic_addr);
let message_addr = MessageAddr::new_message_addr(
[1; KEY_HASH_LENGTH],
[2; TOPIC_NAME_HASH_LENGTH].into(),
3,
);
bytesrepr::test_serialization_roundtrip(&message_addr);
}
}