1use std::ops::Deref;
5
6use ml_kem::EncodedSizeUser;
7use time::OffsetDateTime;
8
9use reach_proc_macros::prosted;
10
11use super::*;
12use crate::{traits::*, wire::EnvelopeId as WireEnvelopeId};
13
14#[derive(Debug)]
15#[prosted(proto::HintedEnvelopeId, Decode, Encode, ProstTraits)]
16pub struct HintedEnvelopeId {
17 pub envelope_id: WireEnvelopeId,
18 pub hint_removal_token: OneSix,
19}
20
21#[cfg(any(feature = "reaching", feature = "reachable"))]
22mod reaching_reachable {
23 use std::{fmt, str};
24
25 use reach_proc_macros::ParticipantPublicKeys;
26 use reach_signatures::Verifies;
27
28 use super::*;
29 use crate::{
30 ParticipantType, error,
31 macros::nested_participant_keys,
32 wire::{
33 EnvelopeId as WireEnvelopeId, RemoveEnvelopeIdHint as WireRemoveEnvelopeIdHint,
34 SealedEnvelopeId as WireSealedEnvelopeId,
35 },
36 };
37
38 impl From<&HintedEnvelopeId> for WireRemoveEnvelopeIdHint {
39 fn from(from: &HintedEnvelopeId) -> Self {
40 Self {
41 envelope_id: from.envelope_id,
42 hint_removal_token: from.hint_removal_token,
43 }
44 }
45 }
46
47 #[prosted(proto::Message, Decode, EncodeOwned, ProstTraitsOwned)]
48 pub struct Message {
49 pub metadata: MessageMetadata,
50 pub chunk: MessageChunk,
51 pub additional_chunks: Vec<MessageChunk>,
52 }
53
54 impl Message {
55 pub fn text(&self) -> Option<Result<&str, str::Utf8Error>> {
56 match &self.chunk.metadata {
57 MessageChunkMetadata::Language(_) => Some(str::from_utf8(&self.chunk.content)),
58 _ => None,
59 }
60 }
61
62 pub fn order(&self) -> &MessageOrder {
63 &self.metadata.order
64 }
65
66 pub fn thread_reference(&self) -> Option<&SixFour> {
67 match &self.metadata.reply_data {
68 ReplyData::ThreadReference(thread_reference) => Some(thread_reference),
69 _ => None,
70 }
71 }
72
73 pub fn reaching_public_keys(&self) -> Option<&ReachingPublicKeys> {
74 match &self.metadata.reply_data {
75 ReplyData::ReachingPublicKeys(reaching_public_keys) => Some(reaching_public_keys),
76 _ => None,
77 }
78 }
79 }
80
81 #[cfg(feature = "reachable")]
82 impl Digestible for Message {
83 fn hashable_bytes(&self) -> Vec<Box<dyn AsRef<[u8]> + '_>> {
84 let mut hashable_bytes: Vec<Box<dyn AsRef<[u8]> + '_>> =
85 Vec::with_capacity(self.additional_chunks.len() + 1);
86 hashable_bytes.push(Box::new(self.chunk.content.as_slice()));
87
88 #[allow(clippy::type_complexity)]
90 let additional_bytes: std::iter::Map<
91 _,
92 fn(&MessageChunk) -> Box<dyn AsRef<[u8]> + '_>,
93 > = self
94 .additional_chunks
95 .iter()
96 .map(|c| Box::new(c.content.as_slice()));
97
98 hashable_bytes.extend(additional_bytes);
99
100 hashable_bytes
101 }
102 }
103
104 impl fmt::Display for Message {
105 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
106 match self.order() {
107 MessageOrder::Datetime(datetime) => writeln!(f, "At {datetime}:")?,
108 MessageOrder::Relative(relative) => {
109 writeln!(f, "Relatively to the first contact {relative}:")?
110 }
111 }
112 for chunk in &self.additional_chunks {
113 match chunk.metadata {
114 MessageChunkMetadata::Language(_) => {
115 let content =
116 str::from_utf8(chunk.content.as_slice()).map_err(|_| fmt::Error)?;
117 write!(f, "{content}")?;
118 }
119 _ => return Err(fmt::Error),
120 }
121 }
122
123 Ok(())
124 }
125 }
126
127 #[derive(Debug)]
128 #[prosted(proto::MessageMetadata, Decode, EncodeOwned)]
129 pub struct MessageMetadata {
130 pub order: MessageOrder,
131 pub reply_data: ReplyData,
132 pub envelope_link: Option<EnvelopeLink>,
133 }
134
135 #[derive(Debug)]
136 pub enum ReplyData {
137 ReachingPublicKeys(Box<ReachingPublicKeys>),
138 ThreadReference(SixFour),
139 }
140
141 impl TryFrom<proto::ReplyData> for ReplyData {
142 type Error = error::DecodeError;
143
144 fn try_from(value: proto::ReplyData) -> Result<Self, Self::Error> {
145 Ok(match value {
146 proto::ReplyData::ReachingPublicKeys(reaching_public_keys) => {
147 ReplyData::ReachingPublicKeys(Box::new(ReachingPublicKeys::try_from(
148 reaching_public_keys,
149 )?))
150 }
151 proto::ReplyData::ThreadReference(bytes) => {
152 ReplyData::ThreadReference(SixFour::try_from(bytes.as_slice())?)
153 }
154 })
155 }
156 }
157
158 impl From<ReplyData> for proto::ReplyData {
159 fn from(from: ReplyData) -> Self {
160 match from {
161 ReplyData::ReachingPublicKeys(reaching_public_keys) => {
162 proto::ReplyData::ReachingPublicKeys(proto::ReachingPublicKeys::from(
163 reaching_public_keys.deref(),
164 ))
165 }
166 ReplyData::ThreadReference(thread_reference) => {
167 proto::ReplyData::ThreadReference(thread_reference.to_vec())
168 }
169 }
170 }
171 }
172
173 #[derive(Debug)]
174 pub enum MessageOrder {
175 Datetime(OffsetDateTime),
176 Relative(u64),
177 }
178
179 impl MessageOrder {
180 pub fn datetime(&self) -> Option<&OffsetDateTime> {
181 match self {
182 MessageOrder::Datetime(datetime) => Some(datetime),
183 _ => None,
184 }
185 }
186
187 pub fn relative(&self) -> Option<u64> {
188 match self {
189 MessageOrder::Relative(relative) => Some(*relative),
190 _ => None,
191 }
192 }
193 }
194
195 impl TryFrom<proto::MessageOrder> for MessageOrder {
196 type Error = error::DecodeError;
197
198 fn try_from(from: proto::MessageOrder) -> Result<Self, Self::Error> {
199 use proto::MessageOrder as Order;
200
201 Ok(match from {
202 Order::Datetime(epoch) => MessageOrder::Datetime(
203 OffsetDateTime::from_unix_timestamp(epoch).map_err(|_| error::DecodeError)?,
204 ),
205 Order::Relative(inner) => MessageOrder::Relative(inner),
206 })
207 }
208 }
209
210 impl From<MessageOrder> for proto::MessageOrder {
211 fn from(from: MessageOrder) -> Self {
212 use proto::MessageOrder as Order;
213
214 match from {
215 MessageOrder::Datetime(inner) => Order::Datetime(inner.unix_timestamp()),
216 MessageOrder::Relative(inner) => Order::Relative(inner),
217 }
218 }
219 }
220
221 impl From<OffsetDateTime> for MessageOrder {
222 fn from(from: OffsetDateTime) -> Self {
223 MessageOrder::Datetime(from)
224 }
225 }
226
227 impl From<u64> for MessageOrder {
228 fn from(from: u64) -> Self {
229 MessageOrder::Relative(from)
230 }
231 }
232
233 #[derive(Debug)]
234 pub enum EnvelopeLink {
235 SealedLink(Box<WireSealedEnvelopeId>),
236 Link(WireEnvelopeId),
237 }
238
239 impl TryFrom<proto::EnvelopeLink> for EnvelopeLink {
240 type Error = error::DecodeError;
241
242 fn try_from(from: proto::EnvelopeLink) -> Result<Self, Self::Error> {
243 use proto::envelope_link as pel;
244
245 match from.envelope_link {
246 Some(pel::EnvelopeLink::SealedLink(inner)) => Ok(EnvelopeLink::SealedLink(
247 Box::new(WireSealedEnvelopeId::decode(inner)?),
248 )),
249 Some(pel::EnvelopeLink::Link(inner)) => {
250 Ok(EnvelopeLink::Link(WireEnvelopeId::decode(inner)?))
251 }
252 _ => Err(error::DecodeError),
253 }
254 }
255 }
256
257 impl From<&EnvelopeLink> for proto::EnvelopeLink {
258 fn from(from: &EnvelopeLink) -> Self {
259 use proto::envelope_link as pel;
260
261 let envelope_link = Some(match from {
262 EnvelopeLink::SealedLink(inner) => {
263 pel::EnvelopeLink::SealedLink(inner.encode_to_vec())
264 }
265 EnvelopeLink::Link(inner) => pel::EnvelopeLink::Link(inner.encode_to_vec()),
266 });
267
268 Self { envelope_link }
269 }
270 }
271
272 #[prosted(proto::MessageChunk, Decode, EncodeOwned)]
273 pub struct MessageChunk {
274 pub metadata: MessageChunkMetadata,
275 pub content: Vec<u8>,
276 }
277
278 impl MessageChunk {
279 pub fn text_only(content: String, language: String) -> MessageChunk {
280 MessageChunk {
281 metadata: MessageChunkMetadata::Language(language),
282 content: content.into_bytes(),
283 }
284 }
285 }
286
287 pub enum MessageChunkMetadata {
288 Language(String),
289 FileChunkMetadata(FileChunkMetadata),
290 }
291
292 impl TryFrom<proto::MessageChunkMetadata> for MessageChunkMetadata {
293 type Error = error::DecodeError;
294
295 fn try_from(from: proto::MessageChunkMetadata) -> Result<Self, Self::Error> {
296 use proto::message_chunk_metadata::Metadata as pmcm;
297
298 Ok(match from.metadata {
299 Some(pmcm::Language(inner)) => MessageChunkMetadata::Language(inner),
300 Some(pmcm::FileChunkMetadata(inner)) => {
301 MessageChunkMetadata::FileChunkMetadata(inner.try_into()?)
302 }
303 _ => return Err(error::DecodeError),
304 })
305 }
306 }
307
308 impl From<MessageChunkMetadata> for proto::MessageChunkMetadata {
309 fn from(from: MessageChunkMetadata) -> Self {
310 use proto::message_chunk_metadata::Metadata as pmcm;
311
312 Self {
313 metadata: Some(match from {
314 MessageChunkMetadata::Language(inner) => pmcm::Language(inner.clone()),
315 MessageChunkMetadata::FileChunkMetadata(inner) => {
316 pmcm::FileChunkMetadata(inner.into())
317 }
318 }),
319 }
320 }
321 }
322
323 #[prosted(proto::FileChunkMetadata, Decode, EncodeOwned)]
324 pub struct FileChunkMetadata {
325 pub complete_file_hash: ThreeTwo,
326 pub file_name: Option<String>,
327 pub mime_type: Option<String>,
328 pub description: Option<String>,
329 pub chunks: Option<u32>,
330 }
331
332 #[prosted(proto::ReachingKeys, Decode, Encode)]
333 pub struct ReachingKeys {
334 pub verifying_keys: ReachingVerifyingKeys,
335 pub public_keys: ReachingPublicKeys,
336 }
337
338 nested_participant_keys!(ReachingKeys);
339
340 #[prosted(proto::ReachingPublicKeys, Decode, Encode)]
341 #[derive(ParticipantPublicKeys, Clone, Debug)]
342 pub struct ReachingPublicKeys {
343 pub ec_public_key: X25519Public,
344 pub pq_public_key: MlKemPublic,
345 pub ec_signature: Ed25519Signature,
346 pub pq_signature: FnDsaSignature,
347 }
348
349 impl Signatures for ReachingPublicKeys {
350 fn ec_signature(&self) -> &Ed25519Signature {
351 &self.ec_signature
352 }
353
354 fn pq_signature(&self) -> &FnDsaSignature {
355 &self.pq_signature
356 }
357 }
358
359 impl Verifies<ReachingPublicKeys> for ReachingVerifyingKeys {}
360}
361
362#[cfg(any(feature = "reaching", feature = "reachable"))]
363pub use reaching_reachable::*;