]> nmode's Git Repositories - signal-cli/blob - lib/src/main/java/org/asamk/signal/manager/api/MessageEnvelope.java
Output sender also for sealed sender messages that fail to decrypt
[signal-cli] / lib / src / main / java / org / asamk / signal / manager / api / MessageEnvelope.java
1 package org.asamk.signal.manager.api;
2
3 import org.asamk.signal.manager.groups.GroupId;
4 import org.asamk.signal.manager.groups.GroupUtils;
5 import org.asamk.signal.manager.helper.RecipientAddressResolver;
6 import org.asamk.signal.manager.storage.recipients.RecipientAddress;
7 import org.asamk.signal.manager.storage.recipients.RecipientResolver;
8 import org.signal.libsignal.metadata.ProtocolException;
9 import org.whispersystems.signalservice.api.messages.SignalServiceAttachment;
10 import org.whispersystems.signalservice.api.messages.SignalServiceAttachmentRemoteId;
11 import org.whispersystems.signalservice.api.messages.SignalServiceContent;
12 import org.whispersystems.signalservice.api.messages.SignalServiceDataMessage;
13 import org.whispersystems.signalservice.api.messages.SignalServiceEnvelope;
14 import org.whispersystems.signalservice.api.messages.SignalServiceGroup;
15 import org.whispersystems.signalservice.api.messages.SignalServiceGroupContext;
16 import org.whispersystems.signalservice.api.messages.SignalServiceReceiptMessage;
17 import org.whispersystems.signalservice.api.messages.SignalServiceTypingMessage;
18 import org.whispersystems.signalservice.api.messages.calls.AnswerMessage;
19 import org.whispersystems.signalservice.api.messages.calls.BusyMessage;
20 import org.whispersystems.signalservice.api.messages.calls.HangupMessage;
21 import org.whispersystems.signalservice.api.messages.calls.IceUpdateMessage;
22 import org.whispersystems.signalservice.api.messages.calls.OfferMessage;
23 import org.whispersystems.signalservice.api.messages.calls.OpaqueMessage;
24 import org.whispersystems.signalservice.api.messages.calls.SignalServiceCallMessage;
25 import org.whispersystems.signalservice.api.messages.multidevice.BlockedListMessage;
26 import org.whispersystems.signalservice.api.messages.multidevice.ContactsMessage;
27 import org.whispersystems.signalservice.api.messages.multidevice.MessageRequestResponseMessage;
28 import org.whispersystems.signalservice.api.messages.multidevice.ReadMessage;
29 import org.whispersystems.signalservice.api.messages.multidevice.SentTranscriptMessage;
30 import org.whispersystems.signalservice.api.messages.multidevice.SignalServiceSyncMessage;
31 import org.whispersystems.signalservice.api.messages.multidevice.ViewOnceOpenMessage;
32 import org.whispersystems.signalservice.api.messages.multidevice.ViewedMessage;
33
34 import java.io.File;
35 import java.util.List;
36 import java.util.Optional;
37 import java.util.Set;
38 import java.util.stream.Collectors;
39
40 public record MessageEnvelope(
41 Optional<RecipientAddress> sourceAddress,
42 int sourceDevice,
43 long timestamp,
44 long serverReceivedTimestamp,
45 long serverDeliveredTimestamp,
46 boolean isUnidentifiedSender,
47 Optional<Receipt> receipt,
48 Optional<Typing> typing,
49 Optional<Data> data,
50 Optional<Sync> sync,
51 Optional<Call> call
52 ) {
53
54 public record Receipt(long when, Type type, List<Long> timestamps) {
55
56 static Receipt from(final SignalServiceReceiptMessage receiptMessage) {
57 return new Receipt(receiptMessage.getWhen(),
58 Type.from(receiptMessage.getType()),
59 receiptMessage.getTimestamps());
60 }
61
62 public enum Type {
63 DELIVERY,
64 READ,
65 VIEWED,
66 UNKNOWN;
67
68 static Type from(SignalServiceReceiptMessage.Type type) {
69 return switch (type) {
70 case DELIVERY -> DELIVERY;
71 case READ -> READ;
72 case VIEWED -> VIEWED;
73 case UNKNOWN -> UNKNOWN;
74 };
75 }
76 }
77 }
78
79 public record Typing(long timestamp, Type type, Optional<GroupId> groupId) {
80
81 public static Typing from(final SignalServiceTypingMessage typingMessage) {
82 return new Typing(typingMessage.getTimestamp(),
83 typingMessage.isTypingStarted() ? Type.STARTED : Type.STOPPED,
84 Optional.ofNullable(typingMessage.getGroupId().transform(GroupId::unknownVersion).orNull()));
85 }
86
87 public enum Type {
88 STARTED,
89 STOPPED,
90 }
91 }
92
93 public record Data(
94 long timestamp,
95 Optional<GroupContext> groupContext,
96 Optional<GroupCallUpdate> groupCallUpdate,
97 Optional<String> body,
98 int expiresInSeconds,
99 boolean isExpirationUpdate,
100 boolean isViewOnce,
101 boolean isEndSession,
102 boolean hasProfileKey,
103 Optional<Reaction> reaction,
104 Optional<Quote> quote,
105 Optional<Payment> payment,
106 List<Attachment> attachments,
107 Optional<Long> remoteDeleteId,
108 Optional<Sticker> sticker,
109 List<SharedContact> sharedContacts,
110 List<Mention> mentions,
111 List<Preview> previews
112 ) {
113
114 static Data from(
115 final SignalServiceDataMessage dataMessage,
116 RecipientResolver recipientResolver,
117 RecipientAddressResolver addressResolver,
118 final AttachmentFileProvider fileProvider
119 ) {
120 return new Data(dataMessage.getTimestamp(),
121 Optional.ofNullable(dataMessage.getGroupContext().transform(GroupContext::from).orNull()),
122 Optional.ofNullable(dataMessage.getGroupCallUpdate().transform(GroupCallUpdate::from).orNull()),
123 Optional.ofNullable(dataMessage.getBody().orNull()),
124 dataMessage.getExpiresInSeconds(),
125 dataMessage.isExpirationUpdate(),
126 dataMessage.isViewOnce(),
127 dataMessage.isEndSession(),
128 dataMessage.getProfileKey().isPresent(),
129 Optional.ofNullable(dataMessage.getReaction()
130 .transform(r -> Reaction.from(r, recipientResolver, addressResolver))
131 .orNull()),
132 Optional.ofNullable(dataMessage.getQuote()
133 .transform(q -> Quote.from(q, recipientResolver, addressResolver, fileProvider))
134 .orNull()),
135 Optional.ofNullable(dataMessage.getPayment()
136 .transform(p -> p.getPaymentNotification().isPresent() ? Payment.from(p) : null)
137 .orNull()),
138 dataMessage.getAttachments()
139 .transform(a -> a.stream().map(as -> Attachment.from(as, fileProvider)).toList())
140 .or(List.of()),
141 Optional.ofNullable(dataMessage.getRemoteDelete()
142 .transform(SignalServiceDataMessage.RemoteDelete::getTargetSentTimestamp)
143 .orNull()),
144 Optional.ofNullable(dataMessage.getSticker().transform(Sticker::from).orNull()),
145 dataMessage.getSharedContacts()
146 .transform(a -> a.stream()
147 .map(sharedContact -> SharedContact.from(sharedContact, fileProvider))
148 .toList())
149 .or(List.of()),
150 dataMessage.getMentions()
151 .transform(a -> a.stream()
152 .map(m -> Mention.from(m, recipientResolver, addressResolver))
153 .toList())
154 .or(List.of()),
155 dataMessage.getPreviews()
156 .transform(a -> a.stream().map(preview -> Preview.from(preview, fileProvider)).toList())
157 .or(List.of()));
158 }
159
160 public record GroupContext(GroupId groupId, boolean isGroupUpdate, int revision) {
161
162 static GroupContext from(SignalServiceGroupContext groupContext) {
163 if (groupContext.getGroupV1().isPresent()) {
164 return new GroupContext(GroupId.v1(groupContext.getGroupV1().get().getGroupId()),
165 groupContext.getGroupV1Type() == SignalServiceGroup.Type.UPDATE,
166 0);
167 } else if (groupContext.getGroupV2().isPresent()) {
168 final var groupV2 = groupContext.getGroupV2().get();
169 return new GroupContext(GroupUtils.getGroupIdV2(groupV2.getMasterKey()),
170 groupV2.hasSignedGroupChange(),
171 groupV2.getRevision());
172 } else {
173 throw new RuntimeException("Invalid group context state");
174 }
175 }
176 }
177
178 public record GroupCallUpdate(String eraId) {
179
180 static GroupCallUpdate from(SignalServiceDataMessage.GroupCallUpdate groupCallUpdate) {
181 return new GroupCallUpdate(groupCallUpdate.getEraId());
182 }
183 }
184
185 public record Reaction(
186 long targetSentTimestamp, RecipientAddress targetAuthor, String emoji, boolean isRemove
187 ) {
188
189 static Reaction from(
190 SignalServiceDataMessage.Reaction reaction,
191 RecipientResolver recipientResolver,
192 RecipientAddressResolver addressResolver
193 ) {
194 return new Reaction(reaction.getTargetSentTimestamp(),
195 addressResolver.resolveRecipientAddress(recipientResolver.resolveRecipient(reaction.getTargetAuthor())),
196 reaction.getEmoji(),
197 reaction.isRemove());
198 }
199 }
200
201 public record Quote(
202 long id,
203 RecipientAddress author,
204 Optional<String> text,
205 List<Mention> mentions,
206 List<Attachment> attachments
207 ) {
208
209 static Quote from(
210 SignalServiceDataMessage.Quote quote,
211 RecipientResolver recipientResolver,
212 RecipientAddressResolver addressResolver,
213 final AttachmentFileProvider fileProvider
214 ) {
215 return new Quote(quote.getId(),
216 addressResolver.resolveRecipientAddress(recipientResolver.resolveRecipient(quote.getAuthor())),
217 Optional.ofNullable(quote.getText()),
218 quote.getMentions() == null
219 ? List.of()
220 : quote.getMentions()
221 .stream()
222 .map(m -> Mention.from(m, recipientResolver, addressResolver))
223 .toList(),
224 quote.getAttachments() == null
225 ? List.of()
226 : quote.getAttachments().stream().map(a -> Attachment.from(a, fileProvider)).toList());
227 }
228 }
229
230 public record Payment(String note, byte[] receipt) {
231
232 static Payment from(SignalServiceDataMessage.Payment payment) {
233 return new Payment(payment.getPaymentNotification().get().getNote(),
234 payment.getPaymentNotification().get().getReceipt());
235 }
236 }
237
238 public record Mention(RecipientAddress recipient, int start, int length) {
239
240 static Mention from(
241 SignalServiceDataMessage.Mention mention,
242 RecipientResolver recipientResolver,
243 RecipientAddressResolver addressResolver
244 ) {
245 return new Mention(addressResolver.resolveRecipientAddress(recipientResolver.resolveRecipient(mention.getAci())),
246 mention.getStart(),
247 mention.getLength());
248 }
249 }
250
251 public record Attachment(
252 Optional<String> id,
253 Optional<File> file,
254 Optional<String> fileName,
255 String contentType,
256 Optional<Long> uploadTimestamp,
257 Optional<Long> size,
258 Optional<byte[]> preview,
259 Optional<Attachment> thumbnail,
260 Optional<String> caption,
261 Optional<Integer> width,
262 Optional<Integer> height,
263 boolean isVoiceNote,
264 boolean isGif,
265 boolean isBorderless
266 ) {
267
268 static Attachment from(SignalServiceAttachment attachment, AttachmentFileProvider fileProvider) {
269 if (attachment.isPointer()) {
270 final var a = attachment.asPointer();
271 return new Attachment(Optional.of(a.getRemoteId().toString()),
272 Optional.of(fileProvider.getFile(a.getRemoteId())),
273 Optional.ofNullable(a.getFileName().orNull()),
274 a.getContentType(),
275 a.getUploadTimestamp() == 0 ? Optional.empty() : Optional.of(a.getUploadTimestamp()),
276 Optional.ofNullable(a.getSize().transform(Integer::longValue).orNull()),
277 Optional.ofNullable(a.getPreview().orNull()),
278 Optional.empty(),
279 Optional.ofNullable(a.getCaption().orNull()),
280 a.getWidth() == 0 ? Optional.empty() : Optional.of(a.getWidth()),
281 a.getHeight() == 0 ? Optional.empty() : Optional.of(a.getHeight()),
282 a.getVoiceNote(),
283 a.isGif(),
284 a.isBorderless());
285 } else {
286 final var a = attachment.asStream();
287 return new Attachment(Optional.empty(),
288 Optional.empty(),
289 Optional.ofNullable(a.getFileName().orNull()),
290 a.getContentType(),
291 a.getUploadTimestamp() == 0 ? Optional.empty() : Optional.of(a.getUploadTimestamp()),
292 Optional.of(a.getLength()),
293 Optional.ofNullable(a.getPreview().orNull()),
294 Optional.empty(),
295 Optional.ofNullable(a.getCaption().orNull()),
296 a.getWidth() == 0 ? Optional.empty() : Optional.of(a.getWidth()),
297 a.getHeight() == 0 ? Optional.empty() : Optional.of(a.getHeight()),
298 a.getVoiceNote(),
299 a.isGif(),
300 a.isBorderless());
301 }
302 }
303
304 static Attachment from(
305 SignalServiceDataMessage.Quote.QuotedAttachment a, final AttachmentFileProvider fileProvider
306 ) {
307 return new Attachment(Optional.empty(),
308 Optional.empty(),
309 Optional.ofNullable(a.getFileName()),
310 a.getContentType(),
311 Optional.empty(),
312 Optional.empty(),
313 Optional.empty(),
314 a.getThumbnail() == null
315 ? Optional.empty()
316 : Optional.of(Attachment.from(a.getThumbnail(), fileProvider)),
317 Optional.empty(),
318 Optional.empty(),
319 Optional.empty(),
320 false,
321 false,
322 false);
323 }
324 }
325
326 public record Sticker(StickerPackId packId, byte[] packKey, int stickerId) {
327
328 static Sticker from(SignalServiceDataMessage.Sticker sticker) {
329 return new Sticker(StickerPackId.deserialize(sticker.getPackId()),
330 sticker.getPackKey(),
331 sticker.getStickerId());
332 }
333 }
334
335 public record SharedContact(
336 Name name,
337 Optional<Avatar> avatar,
338 List<Phone> phone,
339 List<Email> email,
340 List<Address> address,
341 Optional<String> organization
342 ) {
343
344 static SharedContact from(
345 org.whispersystems.signalservice.api.messages.shared.SharedContact sharedContact,
346 final AttachmentFileProvider fileProvider
347 ) {
348 return new SharedContact(Name.from(sharedContact.getName()),
349 Optional.ofNullable(sharedContact.getAvatar()
350 .transform(avatar1 -> Avatar.from(avatar1, fileProvider))
351 .orNull()),
352 sharedContact.getPhone().transform(p -> p.stream().map(Phone::from).toList()).or(List.of()),
353 sharedContact.getEmail().transform(p -> p.stream().map(Email::from).toList()).or(List.of()),
354 sharedContact.getAddress().transform(p -> p.stream().map(Address::from).toList()).or(List.of()),
355 Optional.ofNullable(sharedContact.getOrganization().orNull()));
356 }
357
358 public record Name(
359 Optional<String> display,
360 Optional<String> given,
361 Optional<String> family,
362 Optional<String> prefix,
363 Optional<String> suffix,
364 Optional<String> middle
365 ) {
366
367 static Name from(org.whispersystems.signalservice.api.messages.shared.SharedContact.Name name) {
368 return new Name(Optional.ofNullable(name.getDisplay().orNull()),
369 Optional.ofNullable(name.getGiven().orNull()),
370 Optional.ofNullable(name.getFamily().orNull()),
371 Optional.ofNullable(name.getPrefix().orNull()),
372 Optional.ofNullable(name.getSuffix().orNull()),
373 Optional.ofNullable(name.getMiddle().orNull()));
374 }
375 }
376
377 public record Avatar(Attachment attachment, boolean isProfile) {
378
379 static Avatar from(
380 org.whispersystems.signalservice.api.messages.shared.SharedContact.Avatar avatar,
381 final AttachmentFileProvider fileProvider
382 ) {
383 return new Avatar(Attachment.from(avatar.getAttachment(), fileProvider), avatar.isProfile());
384 }
385 }
386
387 public record Phone(
388 String value, Type type, Optional<String> label
389 ) {
390
391 static Phone from(org.whispersystems.signalservice.api.messages.shared.SharedContact.Phone phone) {
392 return new Phone(phone.getValue(),
393 Type.from(phone.getType()),
394 Optional.ofNullable(phone.getLabel().orNull()));
395 }
396
397 public enum Type {
398 HOME,
399 WORK,
400 MOBILE,
401 CUSTOM;
402
403 static Type from(org.whispersystems.signalservice.api.messages.shared.SharedContact.Phone.Type type) {
404 return switch (type) {
405 case HOME -> HOME;
406 case WORK -> WORK;
407 case MOBILE -> MOBILE;
408 case CUSTOM -> CUSTOM;
409 };
410 }
411 }
412 }
413
414 public record Email(
415 String value, Type type, Optional<String> label
416 ) {
417
418 static Email from(org.whispersystems.signalservice.api.messages.shared.SharedContact.Email email) {
419 return new Email(email.getValue(),
420 Type.from(email.getType()),
421 Optional.ofNullable(email.getLabel().orNull()));
422 }
423
424 public enum Type {
425 HOME,
426 WORK,
427 MOBILE,
428 CUSTOM;
429
430 static Type from(org.whispersystems.signalservice.api.messages.shared.SharedContact.Email.Type type) {
431 return switch (type) {
432 case HOME -> HOME;
433 case WORK -> WORK;
434 case MOBILE -> MOBILE;
435 case CUSTOM -> CUSTOM;
436 };
437 }
438 }
439 }
440
441 public record Address(
442 Type type,
443 Optional<String> label,
444 Optional<String> street,
445 Optional<String> pobox,
446 Optional<String> neighborhood,
447 Optional<String> city,
448 Optional<String> region,
449 Optional<String> postcode,
450 Optional<String> country
451 ) {
452
453 static Address from(org.whispersystems.signalservice.api.messages.shared.SharedContact.PostalAddress address) {
454 return new Address(Address.Type.from(address.getType()),
455 Optional.ofNullable(address.getLabel().orNull()),
456 Optional.ofNullable(address.getLabel().orNull()),
457 Optional.ofNullable(address.getLabel().orNull()),
458 Optional.ofNullable(address.getLabel().orNull()),
459 Optional.ofNullable(address.getLabel().orNull()),
460 Optional.ofNullable(address.getLabel().orNull()),
461 Optional.ofNullable(address.getLabel().orNull()),
462 Optional.ofNullable(address.getLabel().orNull()));
463 }
464
465 public enum Type {
466 HOME,
467 WORK,
468 CUSTOM;
469
470 static Type from(org.whispersystems.signalservice.api.messages.shared.SharedContact.PostalAddress.Type type) {
471 return switch (type) {
472 case HOME -> HOME;
473 case WORK -> WORK;
474 case CUSTOM -> CUSTOM;
475 };
476 }
477 }
478 }
479 }
480
481 public record Preview(String title, String description, long date, String url, Optional<Attachment> image) {
482
483 static Preview from(
484 SignalServiceDataMessage.Preview preview, final AttachmentFileProvider fileProvider
485 ) {
486 return new Preview(preview.getTitle(),
487 preview.getDescription(),
488 preview.getDate(),
489 preview.getUrl(),
490 Optional.ofNullable(preview.getImage()
491 .transform(as -> Attachment.from(as, fileProvider))
492 .orNull()));
493 }
494 }
495 }
496
497 public record Sync(
498 Optional<Sent> sent,
499 Optional<Blocked> blocked,
500 List<Read> read,
501 List<Viewed> viewed,
502 Optional<ViewOnceOpen> viewOnceOpen,
503 Optional<Contacts> contacts,
504 Optional<Groups> groups,
505 Optional<MessageRequestResponse> messageRequestResponse
506 ) {
507
508 public static Sync from(
509 final SignalServiceSyncMessage syncMessage,
510 RecipientResolver recipientResolver,
511 RecipientAddressResolver addressResolver,
512 final AttachmentFileProvider fileProvider
513 ) {
514 return new Sync(Optional.ofNullable(syncMessage.getSent()
515 .transform(s -> Sent.from(s, recipientResolver, addressResolver, fileProvider))
516 .orNull()),
517 Optional.ofNullable(syncMessage.getBlockedList()
518 .transform(b -> Blocked.from(b, recipientResolver, addressResolver))
519 .orNull()),
520 syncMessage.getRead()
521 .transform(r -> r.stream()
522 .map(rm -> Read.from(rm, recipientResolver, addressResolver))
523 .toList())
524 .or(List.of()),
525 syncMessage.getViewed()
526 .transform(r -> r.stream()
527 .map(rm -> Viewed.from(rm, recipientResolver, addressResolver))
528 .toList())
529 .or(List.of()),
530 Optional.ofNullable(syncMessage.getViewOnceOpen()
531 .transform(rm -> ViewOnceOpen.from(rm, recipientResolver, addressResolver))
532 .orNull()),
533 Optional.ofNullable(syncMessage.getContacts().transform(Contacts::from).orNull()),
534 Optional.ofNullable(syncMessage.getGroups().transform(Groups::from).orNull()),
535 Optional.ofNullable(syncMessage.getMessageRequestResponse()
536 .transform(m -> MessageRequestResponse.from(m, recipientResolver, addressResolver))
537 .orNull()));
538 }
539
540 public record Sent(
541 long timestamp,
542 long expirationStartTimestamp,
543 Optional<RecipientAddress> destination,
544 Set<RecipientAddress> recipients,
545 Data message
546 ) {
547
548 static Sent from(
549 SentTranscriptMessage sentMessage,
550 RecipientResolver recipientResolver,
551 RecipientAddressResolver addressResolver,
552 final AttachmentFileProvider fileProvider
553 ) {
554 return new Sent(sentMessage.getTimestamp(),
555 sentMessage.getExpirationStartTimestamp(),
556 Optional.ofNullable(sentMessage.getDestination()
557 .transform(d -> addressResolver.resolveRecipientAddress(recipientResolver.resolveRecipient(
558 d)))
559 .orNull()),
560 sentMessage.getRecipients()
561 .stream()
562 .map(d -> addressResolver.resolveRecipientAddress(recipientResolver.resolveRecipient(d)))
563 .collect(Collectors.toSet()),
564 Data.from(sentMessage.getMessage(), recipientResolver, addressResolver, fileProvider));
565 }
566 }
567
568 public record Blocked(List<RecipientAddress> recipients, List<GroupId> groupIds) {
569
570 static Blocked from(
571 BlockedListMessage blockedListMessage,
572 RecipientResolver recipientResolver,
573 RecipientAddressResolver addressResolver
574 ) {
575 return new Blocked(blockedListMessage.getAddresses()
576 .stream()
577 .map(d -> addressResolver.resolveRecipientAddress(recipientResolver.resolveRecipient(d)))
578 .toList(), blockedListMessage.getGroupIds().stream().map(GroupId::unknownVersion).toList());
579 }
580 }
581
582 public record Read(RecipientAddress sender, long timestamp) {
583
584 static Read from(
585 ReadMessage readMessage,
586 RecipientResolver recipientResolver,
587 RecipientAddressResolver addressResolver
588 ) {
589 return new Read(addressResolver.resolveRecipientAddress(recipientResolver.resolveRecipient(readMessage.getSender())),
590 readMessage.getTimestamp());
591 }
592 }
593
594 public record Viewed(RecipientAddress sender, long timestamp) {
595
596 static Viewed from(
597 ViewedMessage readMessage,
598 RecipientResolver recipientResolver,
599 RecipientAddressResolver addressResolver
600 ) {
601 return new Viewed(addressResolver.resolveRecipientAddress(recipientResolver.resolveRecipient(readMessage.getSender())),
602 readMessage.getTimestamp());
603 }
604 }
605
606 public record ViewOnceOpen(RecipientAddress sender, long timestamp) {
607
608 static ViewOnceOpen from(
609 ViewOnceOpenMessage readMessage,
610 RecipientResolver recipientResolver,
611 RecipientAddressResolver addressResolver
612 ) {
613 return new ViewOnceOpen(addressResolver.resolveRecipientAddress(recipientResolver.resolveRecipient(
614 readMessage.getSender())), readMessage.getTimestamp());
615 }
616 }
617
618 public record Contacts(boolean isComplete) {
619
620 static Contacts from(ContactsMessage contactsMessage) {
621 return new Contacts(contactsMessage.isComplete());
622 }
623 }
624
625 public record Groups() {
626
627 static Groups from(SignalServiceAttachment groupsMessage) {
628 return new Groups();
629 }
630 }
631
632 public record MessageRequestResponse(Type type, Optional<GroupId> groupId, Optional<RecipientAddress> person) {
633
634 static MessageRequestResponse from(
635 MessageRequestResponseMessage messageRequestResponse,
636 RecipientResolver recipientResolver,
637 RecipientAddressResolver addressResolver
638 ) {
639 return new MessageRequestResponse(Type.from(messageRequestResponse.getType()),
640 Optional.ofNullable(messageRequestResponse.getGroupId()
641 .transform(GroupId::unknownVersion)
642 .orNull()),
643 Optional.ofNullable(messageRequestResponse.getPerson()
644 .transform(p -> addressResolver.resolveRecipientAddress(recipientResolver.resolveRecipient(
645 p)))
646 .orNull()));
647 }
648
649 public enum Type {
650 UNKNOWN,
651 ACCEPT,
652 DELETE,
653 BLOCK,
654 BLOCK_AND_DELETE,
655 UNBLOCK_AND_ACCEPT;
656
657 static Type from(MessageRequestResponseMessage.Type type) {
658 return switch (type) {
659 case UNKNOWN -> UNKNOWN;
660 case ACCEPT -> ACCEPT;
661 case DELETE -> DELETE;
662 case BLOCK -> BLOCK;
663 case BLOCK_AND_DELETE -> BLOCK_AND_DELETE;
664 case UNBLOCK_AND_ACCEPT -> UNBLOCK_AND_ACCEPT;
665 };
666 }
667 }
668 }
669 }
670
671 public record Call(
672 Optional<Integer> destinationDeviceId,
673 Optional<GroupId> groupId,
674 Optional<Long> timestamp,
675 Optional<Offer> offer,
676 Optional<Answer> answer,
677 Optional<Hangup> hangup,
678 Optional<Busy> busy,
679 List<IceUpdate> iceUpdate,
680 Optional<Opaque> opaque
681 ) {
682
683 public static Call from(final SignalServiceCallMessage callMessage) {
684 return new Call(Optional.ofNullable(callMessage.getDestinationDeviceId().orNull()),
685 Optional.ofNullable(callMessage.getGroupId().transform(GroupId::unknownVersion).orNull()),
686 Optional.ofNullable(callMessage.getTimestamp().orNull()),
687 Optional.ofNullable(callMessage.getOfferMessage().transform(Offer::from).orNull()),
688 Optional.ofNullable(callMessage.getAnswerMessage().transform(Answer::from).orNull()),
689 Optional.ofNullable(callMessage.getHangupMessage().transform(Hangup::from).orNull()),
690 Optional.ofNullable(callMessage.getBusyMessage().transform(Busy::from).orNull()),
691 callMessage.getIceUpdateMessages()
692 .transform(m -> m.stream().map(IceUpdate::from).toList())
693 .or(List.of()),
694 Optional.ofNullable(callMessage.getOpaqueMessage().transform(Opaque::from).orNull()));
695 }
696
697 public record Offer(long id, String sdp, Type type, byte[] opaque) {
698
699 static Offer from(OfferMessage offerMessage) {
700 return new Offer(offerMessage.getId(),
701 offerMessage.getSdp(),
702 Type.from(offerMessage.getType()),
703 offerMessage.getOpaque());
704 }
705
706 public enum Type {
707 AUDIO_CALL,
708 VIDEO_CALL;
709
710 static Type from(OfferMessage.Type type) {
711 return switch (type) {
712 case AUDIO_CALL -> AUDIO_CALL;
713 case VIDEO_CALL -> VIDEO_CALL;
714 };
715 }
716 }
717 }
718
719 public record Answer(long id, String sdp, byte[] opaque) {
720
721 static Answer from(AnswerMessage answerMessage) {
722 return new Answer(answerMessage.getId(), answerMessage.getSdp(), answerMessage.getOpaque());
723 }
724 }
725
726 public record Busy(long id) {
727
728 static Busy from(BusyMessage busyMessage) {
729 return new Busy(busyMessage.getId());
730 }
731 }
732
733 public record Hangup(long id, Type type, int deviceId, boolean isLegacy) {
734
735 static Hangup from(HangupMessage hangupMessage) {
736 return new Hangup(hangupMessage.getId(),
737 Type.from(hangupMessage.getType()),
738 hangupMessage.getDeviceId(),
739 hangupMessage.isLegacy());
740 }
741
742 public enum Type {
743 NORMAL,
744 ACCEPTED,
745 DECLINED,
746 BUSY,
747 NEED_PERMISSION;
748
749 static Type from(HangupMessage.Type type) {
750 return switch (type) {
751 case NORMAL -> NORMAL;
752 case ACCEPTED -> ACCEPTED;
753 case DECLINED -> DECLINED;
754 case BUSY -> BUSY;
755 case NEED_PERMISSION -> NEED_PERMISSION;
756 };
757 }
758 }
759 }
760
761 public record IceUpdate(long id, String sdp, byte[] opaque) {
762
763 static IceUpdate from(IceUpdateMessage iceUpdateMessage) {
764 return new IceUpdate(iceUpdateMessage.getId(), iceUpdateMessage.getSdp(), iceUpdateMessage.getOpaque());
765 }
766 }
767
768 public record Opaque(byte[] opaque, Urgency urgency) {
769
770 static Opaque from(OpaqueMessage opaqueMessage) {
771 return new Opaque(opaqueMessage.getOpaque(), Urgency.from(opaqueMessage.getUrgency()));
772 }
773
774 public enum Urgency {
775 DROPPABLE,
776 HANDLE_IMMEDIATELY;
777
778 static Urgency from(OpaqueMessage.Urgency urgency) {
779 return switch (urgency) {
780 case DROPPABLE -> DROPPABLE;
781 case HANDLE_IMMEDIATELY -> HANDLE_IMMEDIATELY;
782 };
783 }
784 }
785 }
786 }
787
788 public static MessageEnvelope from(
789 SignalServiceEnvelope envelope,
790 SignalServiceContent content,
791 RecipientResolver recipientResolver,
792 RecipientAddressResolver addressResolver,
793 final AttachmentFileProvider fileProvider,
794 Exception exception
795 ) {
796 final var source = !envelope.isUnidentifiedSender() && envelope.hasSourceUuid()
797 ? recipientResolver.resolveRecipient(envelope.getSourceAddress())
798 : envelope.isUnidentifiedSender() && content != null
799 ? recipientResolver.resolveRecipient(content.getSender())
800 : exception instanceof ProtocolException e
801 ? recipientResolver.resolveRecipient(e.getSender())
802 : null;
803 final var sourceDevice = envelope.hasSourceDevice()
804 ? envelope.getSourceDevice()
805 : content != null
806 ? content.getSenderDevice()
807 : exception instanceof ProtocolException e ? e.getSenderDevice() : 0;
808
809 Optional<Receipt> receipt;
810 Optional<Typing> typing;
811 Optional<Data> data;
812 Optional<Sync> sync;
813 Optional<Call> call;
814 if (content != null) {
815 receipt = Optional.ofNullable(content.getReceiptMessage().transform(Receipt::from).orNull());
816 typing = Optional.ofNullable(content.getTypingMessage().transform(Typing::from).orNull());
817 data = Optional.ofNullable(content.getDataMessage()
818 .transform(dataMessage -> Data.from(dataMessage, recipientResolver, addressResolver, fileProvider))
819 .orNull());
820 sync = Optional.ofNullable(content.getSyncMessage()
821 .transform(s -> Sync.from(s, recipientResolver, addressResolver, fileProvider))
822 .orNull());
823 call = Optional.ofNullable(content.getCallMessage().transform(Call::from).orNull());
824 } else {
825 receipt = envelope.isReceipt() ? Optional.of(new Receipt(envelope.getServerReceivedTimestamp(),
826 Receipt.Type.DELIVERY,
827 List.of(envelope.getTimestamp()))) : Optional.empty();
828 typing = Optional.empty();
829 data = Optional.empty();
830 sync = Optional.empty();
831 call = Optional.empty();
832 }
833
834 return new MessageEnvelope(source == null
835 ? Optional.empty()
836 : Optional.of(addressResolver.resolveRecipientAddress(source)),
837 sourceDevice,
838 envelope.getTimestamp(),
839 envelope.getServerReceivedTimestamp(),
840 envelope.getServerDeliveredTimestamp(),
841 envelope.isUnidentifiedSender(),
842 receipt,
843 typing,
844 data,
845 sync,
846 call);
847 }
848
849 public interface AttachmentFileProvider {
850
851 File getFile(SignalServiceAttachmentRemoteId attachmentRemoteId);
852 }
853 }