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