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