xmtp_proto/types/
topic.rs1use std::{
2 fmt::{Debug, Display},
3 ops::Deref,
4};
5
6use smallvec::SmallVec;
7
8use crate::{ConversionError, types::InstallationId, xmtp::xmtpv4::envelopes::AuthenticatedData};
9
10type TopicBytes = SmallVec<[u8; 33]>;
13
14#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
15#[repr(u8)]
16#[non_exhaustive]
17pub enum TopicKind {
18 GroupMessagesV1 = 0,
19 WelcomeMessagesV1 = 1,
20 IdentityUpdatesV1 = 2,
21 KeyPackagesV1 = 3,
22}
23
24impl TryFrom<u8> for TopicKind {
25 type Error = crate::ConversionError;
26
27 fn try_from(value: u8) -> Result<Self, Self::Error> {
28 match value {
29 0 => Ok(TopicKind::GroupMessagesV1),
30 1 => Ok(TopicKind::WelcomeMessagesV1),
31 2 => Ok(TopicKind::IdentityUpdatesV1),
32 3 => Ok(TopicKind::KeyPackagesV1),
33 i => Err(ConversionError::InvalidValue {
34 item: "u8",
35 expected: "an unsigned integer in the range 0-3",
36 got: i.to_string(),
37 }),
38 }
39 }
40}
41
42impl Display for TopicKind {
43 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
44 use TopicKind::*;
45 match self {
46 GroupMessagesV1 => write!(f, "group_message_v1"),
47 WelcomeMessagesV1 => write!(f, "welcome_message_v1"),
48 IdentityUpdatesV1 => write!(f, "identity_updates_v1"),
49 KeyPackagesV1 => write!(f, "key_packages_v1"),
50 }
51 }
52}
53
54impl TopicKind {
55 fn build<B: AsRef<[u8]>>(&self, bytes: B) -> TopicBytes {
56 let bytes = bytes.as_ref();
57 let mut topic = TopicBytes::new();
58 topic.push(*self as u8);
59 topic.extend_from_slice(bytes);
60 topic
61 }
62
63 pub fn create<B: AsRef<[u8]>>(&self, bytes: B) -> Topic {
64 Topic {
65 inner: self.build(bytes),
66 }
67 }
68}
69
70#[derive(Clone, PartialEq, Eq, Hash)]
73pub struct Topic {
74 inner: TopicBytes,
75}
76
77impl Topic {
78 pub fn new(kind: TopicKind, bytes: Vec<u8>) -> Self {
79 Self {
80 inner: kind.build(bytes),
81 }
82 }
83
84 pub fn new_group_message(group_id: impl AsRef<[u8]>) -> Self {
86 TopicKind::GroupMessagesV1.create(group_id)
87 }
88
89 pub fn new_identity_update(inbox_id: impl AsRef<[u8]>) -> Self {
94 TopicKind::IdentityUpdatesV1.create(inbox_id)
95 }
96
97 pub fn new_welcome_message(installation_id: InstallationId) -> Self {
100 TopicKind::WelcomeMessagesV1.create(installation_id)
101 }
102
103 pub fn new_key_package(installation_id: impl AsRef<[u8]>) -> Self {
106 TopicKind::KeyPackagesV1.create(installation_id.as_ref())
107 }
108
109 pub fn kind(&self) -> TopicKind {
110 self.inner[0]
111 .try_into()
112 .expect("A topic must always be built with a valid `TopicKind`")
113 }
114
115 pub fn identifier(&self) -> &[u8] {
117 &self.inner[1..]
118 }
119
120 pub fn cloned_vec(&self) -> Vec<u8> {
122 self.inner.clone().to_vec()
123 }
124
125 pub fn to_bytes(self) -> TopicBytes {
127 self.inner
128 }
129
130 pub fn identity_updates(&self) -> Option<&Topic> {
135 if self.kind() == TopicKind::IdentityUpdatesV1 {
136 Some(self)
137 } else {
138 None
139 }
140 }
141
142 pub fn group_message_v1(&self) -> Option<&Topic> {
147 if self.kind() == TopicKind::GroupMessagesV1 {
148 Some(self)
149 } else {
150 None
151 }
152 }
153
154 pub fn welcome_message_v1(&self) -> Option<&Topic> {
159 if self.kind() == TopicKind::WelcomeMessagesV1 {
160 Some(self)
161 } else {
162 None
163 }
164 }
165
166 pub fn key_packages_v1(&self) -> Option<&Topic> {
171 if self.kind() == TopicKind::KeyPackagesV1 {
172 Some(self)
173 } else {
174 None
175 }
176 }
177
178 #[cfg(any(feature = "test-utils", test))]
183 pub fn from_bytes(bytes: impl AsRef<[u8]>) -> Self {
184 Self {
185 inner: SmallVec::from_slice(bytes.as_ref()),
186 }
187 }
188}
189
190impl TryFrom<Vec<u8>> for Topic {
191 type Error = ConversionError;
192
193 fn try_from(value: Vec<u8>) -> Result<Self, Self::Error> {
194 if let Some(byte) = value.first() {
195 let kind = TopicKind::try_from(*byte)?;
196 Ok(Topic::new(kind, value[1..].to_vec()))
197 } else {
198 Err(ConversionError::InvalidValue {
199 item: "Topic",
200 expected: "a byte array where the first byte is a valid TopicKind",
201 got: hex::encode(value),
202 })
203 }
204 }
205}
206
207impl From<Topic> for Vec<u8> {
208 fn from(topic: Topic) -> Vec<u8> {
209 topic.to_bytes().to_vec()
210 }
211}
212
213impl Debug for Topic {
214 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
215 f.debug_struct("Topic")
216 .field("kind", &self.kind())
217 .field("bytes", &hex::encode(self.identifier()))
218 .finish()
219 }
220}
221
222impl Display for Topic {
223 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
224 write!(f, "[{}/{}]", self.kind(), hex::encode(self.identifier()))
225 }
226}
227
228impl Deref for Topic {
229 type Target = [u8];
230
231 fn deref(&self) -> &Self::Target {
232 self.inner.deref()
233 }
234}
235
236impl<T> AsRef<T> for Topic
237where
238 T: ?Sized,
239 <Topic as Deref>::Target: AsRef<T>,
240{
241 fn as_ref(&self) -> &T {
242 self.deref().as_ref()
243 }
244}
245
246impl AsRef<Topic> for Topic {
247 fn as_ref(&self) -> &Topic {
248 self
249 }
250}
251
252impl AuthenticatedData {
253 pub fn with_topic(topic: Topic) -> AuthenticatedData {
254 AuthenticatedData {
255 target_topic: topic.into(),
256 depends_on: None,
257 }
258 }
259}