xmtp_proto/types/
topic.rs1use serde::{Deserialize, Deserializer, Serialize, Serializer};
2use std::{
3 fmt::{Debug, Display},
4 ops::Deref,
5};
6
7use smallvec::SmallVec;
8
9use crate::{ConversionError, types::InstallationId, xmtp::xmtpv4::envelopes::AuthenticatedData};
10
11type TopicBytes = SmallVec<[u8; 33]>;
14
15#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
16#[repr(u8)]
17#[non_exhaustive]
18pub enum TopicKind {
19 GroupMessagesV1 = 0,
20 WelcomeMessagesV1 = 1,
21 IdentityUpdatesV1 = 2,
22 KeyPackagesV1 = 3,
23}
24
25impl TryFrom<u8> for TopicKind {
26 type Error = crate::ConversionError;
27
28 fn try_from(value: u8) -> Result<Self, Self::Error> {
29 match value {
30 0 => Ok(TopicKind::GroupMessagesV1),
31 1 => Ok(TopicKind::WelcomeMessagesV1),
32 2 => Ok(TopicKind::IdentityUpdatesV1),
33 3 => Ok(TopicKind::KeyPackagesV1),
34 i => Err(ConversionError::InvalidValue {
35 item: "u8",
36 expected: "an unsigned integer in the range 0-3",
37 got: i.to_string(),
38 }),
39 }
40 }
41}
42
43impl Display for TopicKind {
44 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
45 use TopicKind::*;
46 match self {
47 GroupMessagesV1 => write!(f, "group_message_v1"),
48 WelcomeMessagesV1 => write!(f, "welcome_message_v1"),
49 IdentityUpdatesV1 => write!(f, "identity_updates_v1"),
50 KeyPackagesV1 => write!(f, "key_packages_v1"),
51 }
52 }
53}
54
55impl TopicKind {
56 fn build<B: AsRef<[u8]>>(&self, bytes: B) -> TopicBytes {
57 let bytes = bytes.as_ref();
58 let mut topic = TopicBytes::new();
59 topic.push(*self as u8);
60 topic.extend_from_slice(bytes);
61 topic
62 }
63
64 pub fn create<B: AsRef<[u8]>>(&self, bytes: B) -> Topic {
65 Topic {
66 inner: self.build(bytes),
67 }
68 }
69}
70
71#[derive(Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
74#[serde(transparent)]
75pub struct Topic {
76 #[serde(serialize_with = "to_hex", deserialize_with = "from_hex")]
77 inner: TopicBytes,
78}
79
80fn to_hex<S>(bytes: &TopicBytes, serializer: S) -> Result<S::Ok, S::Error>
81where
82 S: Serializer,
83{
84 serializer.serialize_str(&hex::encode(bytes.as_slice()))
85}
86
87fn from_hex<'de, D>(deserializer: D) -> Result<TopicBytes, D::Error>
88where
89 D: Deserializer<'de>,
90{
91 let s: &str = Deserialize::deserialize(deserializer)?;
92 hex::decode(s)
93 .map(SmallVec::from_vec)
94 .map_err(serde::de::Error::custom)
95}
96
97impl Topic {
98 pub fn new(kind: TopicKind, bytes: Vec<u8>) -> Self {
99 Self {
100 inner: kind.build(bytes),
101 }
102 }
103
104 pub fn new_group_message(group_id: impl AsRef<[u8]>) -> Self {
106 TopicKind::GroupMessagesV1.create(group_id)
107 }
108
109 pub fn new_identity_update(inbox_id: impl AsRef<[u8]>) -> Self {
114 TopicKind::IdentityUpdatesV1.create(inbox_id)
115 }
116
117 pub fn new_welcome_message(installation_id: InstallationId) -> Self {
120 TopicKind::WelcomeMessagesV1.create(installation_id)
121 }
122
123 pub fn new_key_package(installation_id: impl AsRef<[u8]>) -> Self {
126 TopicKind::KeyPackagesV1.create(installation_id.as_ref())
127 }
128
129 pub fn kind(&self) -> TopicKind {
130 self.inner[0]
131 .try_into()
132 .expect("A topic must always be built with a valid `TopicKind`")
133 }
134
135 pub fn identifier(&self) -> &[u8] {
137 &self.inner[1..]
138 }
139
140 pub fn cloned_vec(&self) -> Vec<u8> {
142 self.inner.clone().to_vec()
143 }
144
145 pub fn to_bytes(self) -> TopicBytes {
147 self.inner
148 }
149
150 pub fn identity_updates(&self) -> Option<&Topic> {
155 if self.kind() == TopicKind::IdentityUpdatesV1 {
156 Some(self)
157 } else {
158 None
159 }
160 }
161
162 pub fn group_message_v1(&self) -> Option<&Topic> {
167 if self.kind() == TopicKind::GroupMessagesV1 {
168 Some(self)
169 } else {
170 None
171 }
172 }
173
174 pub fn welcome_message_v1(&self) -> Option<&Topic> {
179 if self.kind() == TopicKind::WelcomeMessagesV1 {
180 Some(self)
181 } else {
182 None
183 }
184 }
185
186 pub fn key_packages_v1(&self) -> Option<&Topic> {
191 if self.kind() == TopicKind::KeyPackagesV1 {
192 Some(self)
193 } else {
194 None
195 }
196 }
197
198 #[cfg(any(feature = "test-utils", test))]
203 pub fn from_bytes(bytes: impl AsRef<[u8]>) -> Self {
204 Self {
205 inner: SmallVec::from_slice(bytes.as_ref()),
206 }
207 }
208}
209
210impl TryFrom<Vec<u8>> for Topic {
211 type Error = ConversionError;
212
213 fn try_from(value: Vec<u8>) -> Result<Self, Self::Error> {
214 if let Some(byte) = value.first() {
215 let kind = TopicKind::try_from(*byte)?;
216 Ok(Topic::new(kind, value[1..].to_vec()))
217 } else {
218 Err(ConversionError::InvalidValue {
219 item: "Topic",
220 expected: "a byte array where the first byte is a valid TopicKind",
221 got: hex::encode(value),
222 })
223 }
224 }
225}
226
227impl From<Topic> for Vec<u8> {
228 fn from(topic: Topic) -> Vec<u8> {
229 topic.to_bytes().to_vec()
230 }
231}
232
233impl Debug for Topic {
234 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
235 f.debug_struct("Topic")
236 .field("kind", &self.kind())
237 .field("bytes", &hex::encode(self.identifier()))
238 .finish()
239 }
240}
241
242impl Display for Topic {
243 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
244 write!(f, "[{}/{}]", self.kind(), hex::encode(self.identifier()))
245 }
246}
247
248impl Deref for Topic {
249 type Target = [u8];
250
251 fn deref(&self) -> &Self::Target {
252 self.inner.deref()
253 }
254}
255
256impl<T> AsRef<T> for Topic
257where
258 T: ?Sized,
259 <Topic as Deref>::Target: AsRef<T>,
260{
261 fn as_ref(&self) -> &T {
262 self.deref().as_ref()
263 }
264}
265
266impl AsRef<Topic> for Topic {
267 fn as_ref(&self) -> &Topic {
268 self
269 }
270}
271
272impl AuthenticatedData {
273 pub fn with_topic(topic: Topic) -> AuthenticatedData {
274 AuthenticatedData {
275 target_topic: topic.into(),
276 depends_on: None,
277 }
278 }
279}