1mod error;
4mod messages;
5mod topics;
6
7pub use error::FromStrError;
8pub use messages::{Message, MessageChecksum, MessagePayload, Messages};
9pub use topics::{
10 MessageTopicOperation, MessageTopicSummary, TopicNameHash, TOPIC_NAME_HASH_LENGTH,
11};
12
13use crate::{
14 bytesrepr::{self, FromBytes, ToBytes},
15 EntityAddr,
16};
17
18use alloc::{string::String, vec::Vec};
19use core::fmt::{Debug, Display, Formatter};
20
21#[cfg(feature = "datasize")]
22use datasize::DataSize;
23#[cfg(any(feature = "testing", test))]
24use rand::{
25 distributions::{Distribution, Standard},
26 Rng,
27};
28#[cfg(feature = "json-schema")]
29use schemars::JsonSchema;
30use serde::{Deserialize, Serialize};
31
32const TOPIC_FORMATTED_STRING_PREFIX: &str = "topic-";
33const MESSAGE_ADDR_PREFIX: &str = "message-";
34
35#[derive(PartialOrd, Ord, PartialEq, Eq, Hash, Clone, Copy, Serialize, Deserialize, Debug)]
37#[cfg_attr(feature = "json-schema", derive(JsonSchema))]
38#[cfg_attr(feature = "datasize", derive(DataSize))]
39pub struct MessageAddr {
40 entity_addr: EntityAddr,
42 topic_name_hash: TopicNameHash,
44 message_index: Option<u32>,
46}
47
48impl MessageAddr {
49 pub const fn new_topic_addr(entity_addr: EntityAddr, topic_name_hash: TopicNameHash) -> Self {
52 Self {
53 entity_addr,
54 topic_name_hash,
55 message_index: None,
56 }
57 }
58
59 pub const fn new_message_addr(
62 entity_addr: EntityAddr,
63 topic_name_hash: TopicNameHash,
64 message_index: u32,
65 ) -> Self {
66 Self {
67 entity_addr,
68 topic_name_hash,
69 message_index: Some(message_index),
70 }
71 }
72
73 pub fn to_formatted_string(self) -> String {
75 match self.message_index {
76 Some(index) => {
77 format!(
78 "{}{}-{}-{:x}",
79 MESSAGE_ADDR_PREFIX,
80 self.entity_addr,
81 self.topic_name_hash.to_formatted_string(),
82 index,
83 )
84 }
85 None => {
86 format!(
87 "{}{}{}-{}",
88 MESSAGE_ADDR_PREFIX,
89 TOPIC_FORMATTED_STRING_PREFIX,
90 self.entity_addr,
91 self.topic_name_hash.to_formatted_string(),
92 )
93 }
94 }
95 }
96
97 pub fn from_formatted_str(input: &str) -> Result<Self, FromStrError> {
99 let remainder = input
100 .strip_prefix(MESSAGE_ADDR_PREFIX)
101 .ok_or(FromStrError::InvalidPrefix)?;
102
103 let (remainder, message_index) = match remainder.strip_prefix(TOPIC_FORMATTED_STRING_PREFIX)
104 {
105 Some(topic_string) => (topic_string, None),
106 None => {
107 let (remainder, message_index_str) = remainder
108 .rsplit_once('-')
109 .ok_or(FromStrError::MissingMessageIndex)?;
110 (remainder, Some(u32::from_str_radix(message_index_str, 16)?))
111 }
112 };
113
114 let (entity_addr_str, topic_name_hash_str) = remainder
115 .rsplit_once('-')
116 .ok_or(FromStrError::MissingMessageIndex)?;
117
118 let entity_addr = EntityAddr::from_formatted_str(entity_addr_str)
119 .map_err(FromStrError::EntityAddrParseError)?;
120
121 let topic_name_hash = TopicNameHash::from_formatted_str(topic_name_hash_str)?;
122 Ok(MessageAddr {
123 entity_addr,
124 topic_name_hash,
125 message_index,
126 })
127 }
128
129 pub fn entity_addr(&self) -> EntityAddr {
131 self.entity_addr
132 }
133
134 pub fn topic_name_hash(&self) -> TopicNameHash {
136 self.topic_name_hash
137 }
138
139 pub fn message_index(&self) -> Option<u32> {
142 self.message_index
143 }
144}
145
146impl Display for MessageAddr {
147 fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
148 match self.message_index {
149 Some(index) => {
150 write!(
151 f,
152 "{}-{}-{:x}",
153 self.entity_addr, self.topic_name_hash, index,
154 )
155 }
156 None => {
157 write!(f, "{}-{}", self.entity_addr, self.topic_name_hash)
158 }
159 }
160 }
161}
162
163impl ToBytes for MessageAddr {
164 fn to_bytes(&self) -> Result<Vec<u8>, bytesrepr::Error> {
165 let mut buffer = bytesrepr::allocate_buffer(self)?;
166 buffer.append(&mut self.entity_addr.to_bytes()?);
167 buffer.append(&mut self.topic_name_hash.to_bytes()?);
168 buffer.append(&mut self.message_index.to_bytes()?);
169 Ok(buffer)
170 }
171
172 fn serialized_length(&self) -> usize {
173 self.entity_addr.serialized_length()
174 + self.topic_name_hash.serialized_length()
175 + self.message_index.serialized_length()
176 }
177}
178
179impl FromBytes for MessageAddr {
180 fn from_bytes(bytes: &[u8]) -> Result<(Self, &[u8]), bytesrepr::Error> {
181 let (entity_addr, rem) = FromBytes::from_bytes(bytes)?;
182 let (topic_hash, rem) = FromBytes::from_bytes(rem)?;
183 let (message_index, rem) = FromBytes::from_bytes(rem)?;
184 Ok((
185 MessageAddr {
186 entity_addr,
187 topic_name_hash: topic_hash,
188 message_index,
189 },
190 rem,
191 ))
192 }
193}
194
195#[cfg(any(feature = "testing", test))]
196impl Distribution<MessageAddr> for Standard {
197 fn sample<R: Rng + ?Sized>(&self, rng: &mut R) -> MessageAddr {
198 MessageAddr {
199 entity_addr: rng.gen(),
200 topic_name_hash: rng.gen(),
201 message_index: rng.gen(),
202 }
203 }
204}
205
206#[cfg(test)]
207mod tests {
208 use crate::{bytesrepr, KEY_HASH_LENGTH};
209
210 use super::{topics::TOPIC_NAME_HASH_LENGTH, *};
211
212 #[test]
213 fn serialization_roundtrip() {
214 let topic_addr = MessageAddr::new_topic_addr(
215 EntityAddr::SmartContract([1; KEY_HASH_LENGTH]),
216 [2; TOPIC_NAME_HASH_LENGTH].into(),
217 );
218 bytesrepr::test_serialization_roundtrip(&topic_addr);
219
220 let message_addr = MessageAddr::new_message_addr(
221 EntityAddr::SmartContract([1; KEY_HASH_LENGTH]),
222 [2; TOPIC_NAME_HASH_LENGTH].into(),
223 3,
224 );
225 bytesrepr::test_serialization_roundtrip(&message_addr);
226 }
227}