use of org.whispersystems.signalservice.api.push.SignalServiceAddress in project Signal-Android by WhisperSystems.
the class SignalServiceAddressProtobufSerializerTest method serialize_and_deserialize_uuid_address.
@Test
public void serialize_and_deserialize_uuid_address() {
SignalServiceAddress address = new SignalServiceAddress(ServiceId.from(UUID.randomUUID()), Optional.absent());
AddressProto addressProto = org.whispersystems.signalservice.internal.serialize.SignalServiceAddressProtobufSerializer.toProtobuf(address);
SignalServiceAddress deserialized = org.whispersystems.signalservice.internal.serialize.SignalServiceAddressProtobufSerializer.fromProtobuf(addressProto);
assertEquals(address, deserialized);
}
use of org.whispersystems.signalservice.api.push.SignalServiceAddress in project Signal-Android by WhisperSystems.
the class GroupSendUtil method sendMessage.
/**
* Handles all of the logic of sending to a group. Will do sender key sends and legacy 1:1 sends as-needed, and give you back a list of
* {@link SendMessageResult}s just like we're used to.
*
* @param groupId The groupId of the group you're sending to, or null if you're sending to a collection of recipients not joined by a group.
* @param isRecipientUpdate True if you've already sent this message to some recipients in the past, otherwise false.
*/
@WorkerThread
private static List<SendMessageResult> sendMessage(@NonNull Context context, @Nullable GroupId.V2 groupId, @Nullable MessageId relatedMessageId, @NonNull List<Recipient> allTargets, boolean isRecipientUpdate, @NonNull SendOperation sendOperation, @Nullable CancelationSignal cancelationSignal) throws IOException, UntrustedIdentityException {
Log.i(TAG, "Starting group send. GroupId: " + (groupId != null ? groupId.toString() : "none") + ", RelatedMessageId: " + (relatedMessageId != null ? relatedMessageId.toString() : "none") + ", Targets: " + allTargets.size() + ", RecipientUpdate: " + isRecipientUpdate + ", Operation: " + sendOperation.getClass().getSimpleName());
Set<Recipient> unregisteredTargets = allTargets.stream().filter(Recipient::isUnregistered).collect(Collectors.toSet());
List<Recipient> registeredTargets = allTargets.stream().filter(r -> !unregisteredTargets.contains(r)).collect(Collectors.toList());
RecipientData recipients = new RecipientData(context, registeredTargets);
Optional<GroupRecord> groupRecord = groupId != null ? SignalDatabase.groups().getGroup(groupId) : Optional.absent();
List<Recipient> senderKeyTargets = new LinkedList<>();
List<Recipient> legacyTargets = new LinkedList<>();
for (Recipient recipient : registeredTargets) {
Optional<UnidentifiedAccessPair> access = recipients.getAccessPair(recipient.getId());
boolean validMembership = groupRecord.isPresent() && groupRecord.get().getMembers().contains(recipient.getId());
if (recipient.getSenderKeyCapability() == Recipient.Capability.SUPPORTED && recipient.hasServiceId() && access.isPresent() && access.get().getTargetUnidentifiedAccess().isPresent() && validMembership) {
senderKeyTargets.add(recipient);
} else {
legacyTargets.add(recipient);
}
}
if (groupId == null) {
Log.i(TAG, "Recipients not in a group. Using legacy.");
legacyTargets.addAll(senderKeyTargets);
senderKeyTargets.clear();
} else if (Recipient.self().getSenderKeyCapability() != Recipient.Capability.SUPPORTED) {
Log.i(TAG, "All of our devices do not support sender key. Using legacy.");
legacyTargets.addAll(senderKeyTargets);
senderKeyTargets.clear();
} else if (SignalStore.internalValues().removeSenderKeyMinimum()) {
Log.i(TAG, "Sender key minimum removed. Using for " + senderKeyTargets.size() + " recipients.");
} else if (senderKeyTargets.size() < 2) {
Log.i(TAG, "Too few sender-key-capable users (" + senderKeyTargets.size() + "). Doing all legacy sends.");
legacyTargets.addAll(senderKeyTargets);
senderKeyTargets.clear();
} else {
Log.i(TAG, "Can use sender key for " + senderKeyTargets.size() + "/" + allTargets.size() + " recipients.");
}
if (relatedMessageId != null) {
SignalLocalMetrics.GroupMessageSend.onSenderKeyStarted(relatedMessageId.getId());
}
List<SendMessageResult> allResults = new ArrayList<>(allTargets.size());
SignalServiceMessageSender messageSender = ApplicationDependencies.getSignalServiceMessageSender();
if (senderKeyTargets.size() > 0 && groupId != null) {
DistributionId distributionId = SignalDatabase.groups().getOrCreateDistributionId(groupId);
long keyCreateTime = SenderKeyUtil.getCreateTimeForOurKey(context, distributionId);
long keyAge = System.currentTimeMillis() - keyCreateTime;
if (keyCreateTime != -1 && keyAge > FeatureFlags.senderKeyMaxAge()) {
Log.w(TAG, "DistributionId " + distributionId + " was created at " + keyCreateTime + " and is " + (keyAge) + " ms old (~" + TimeUnit.MILLISECONDS.toDays(keyAge) + " days). Rotating.");
SenderKeyUtil.rotateOurKey(context, distributionId);
}
try {
List<SignalServiceAddress> targets = senderKeyTargets.stream().map(r -> recipients.getAddress(r.getId())).collect(Collectors.toList());
List<UnidentifiedAccess> access = senderKeyTargets.stream().map(r -> recipients.requireAccess(r.getId())).collect(Collectors.toList());
List<SendMessageResult> results = sendOperation.sendWithSenderKey(messageSender, distributionId, targets, access, isRecipientUpdate);
allResults.addAll(results);
int successCount = (int) results.stream().filter(SendMessageResult::isSuccess).count();
Log.d(TAG, "Successfully sent using sender key to " + successCount + "/" + targets.size() + " sender key targets.");
if (sendOperation.shouldIncludeInMessageLog()) {
SignalDatabase.messageLog().insertIfPossible(sendOperation.getSentTimestamp(), senderKeyTargets, results, sendOperation.getContentHint(), sendOperation.getRelatedMessageId());
}
if (relatedMessageId != null) {
SignalLocalMetrics.GroupMessageSend.onSenderKeyMslInserted(relatedMessageId.getId());
}
} catch (InvalidUnidentifiedAccessHeaderException e) {
Log.w(TAG, "Someone had a bad UD header. Falling back to legacy sends.", e);
legacyTargets.addAll(senderKeyTargets);
} catch (NoSessionException e) {
Log.w(TAG, "No session. Falling back to legacy sends.", e);
legacyTargets.addAll(senderKeyTargets);
} catch (InvalidKeyException e) {
Log.w(TAG, "Invalid key. Falling back to legacy sends.", e);
legacyTargets.addAll(senderKeyTargets);
} catch (InvalidRegistrationIdException e) {
Log.w(TAG, "Invalid registrationId. Falling back to legacy sends.", e);
legacyTargets.addAll(senderKeyTargets);
} catch (NotFoundException e) {
Log.w(TAG, "Someone was unregistered. Falling back to legacy sends.", e);
legacyTargets.addAll(senderKeyTargets);
}
} else if (relatedMessageId != null) {
SignalLocalMetrics.GroupMessageSend.onSenderKeyShared(relatedMessageId.getId());
SignalLocalMetrics.GroupMessageSend.onSenderKeyEncrypted(relatedMessageId.getId());
SignalLocalMetrics.GroupMessageSend.onSenderKeyMessageSent(relatedMessageId.getId());
SignalLocalMetrics.GroupMessageSend.onSenderKeySyncSent(relatedMessageId.getId());
SignalLocalMetrics.GroupMessageSend.onSenderKeyMslInserted(relatedMessageId.getId());
}
if (cancelationSignal != null && cancelationSignal.isCanceled()) {
throw new CancelationException();
}
boolean onlyTargetIsSelfWithLinkedDevice = legacyTargets.isEmpty() && senderKeyTargets.isEmpty() && TextSecurePreferences.isMultiDevice(context);
if (legacyTargets.size() > 0 || onlyTargetIsSelfWithLinkedDevice) {
if (legacyTargets.size() > 0) {
Log.i(TAG, "Need to do " + legacyTargets.size() + " legacy sends.");
} else {
Log.i(TAG, "Need to do a legacy send to send a sync message for a group of only ourselves.");
}
List<SignalServiceAddress> targets = legacyTargets.stream().map(r -> recipients.getAddress(r.getId())).collect(Collectors.toList());
List<Optional<UnidentifiedAccessPair>> access = legacyTargets.stream().map(r -> recipients.getAccessPair(r.getId())).collect(Collectors.toList());
boolean recipientUpdate = isRecipientUpdate || allResults.size() > 0;
final MessageSendLogDatabase messageLogDatabase = SignalDatabase.messageLog();
final AtomicLong entryId = new AtomicLong(-1);
final boolean includeInMessageLog = sendOperation.shouldIncludeInMessageLog();
List<SendMessageResult> results = sendOperation.sendLegacy(messageSender, targets, access, recipientUpdate, result -> {
if (!includeInMessageLog) {
return;
}
synchronized (entryId) {
if (entryId.get() == -1) {
entryId.set(messageLogDatabase.insertIfPossible(recipients.requireRecipientId(result.getAddress()), sendOperation.getSentTimestamp(), result, sendOperation.getContentHint(), sendOperation.getRelatedMessageId()));
} else {
messageLogDatabase.addRecipientToExistingEntryIfPossible(entryId.get(), recipients.requireRecipientId(result.getAddress()), result);
}
}
}, cancelationSignal);
allResults.addAll(results);
int successCount = (int) results.stream().filter(SendMessageResult::isSuccess).count();
Log.d(TAG, "Successfully sent using 1:1 to " + successCount + "/" + targets.size() + " legacy targets.");
} else if (relatedMessageId != null) {
SignalLocalMetrics.GroupMessageSend.onLegacyMessageSent(relatedMessageId.getId());
SignalLocalMetrics.GroupMessageSend.onLegacySyncFinished(relatedMessageId.getId());
}
if (unregisteredTargets.size() > 0) {
Log.w(TAG, "There are " + unregisteredTargets.size() + " unregistered targets. Including failure results.");
List<SendMessageResult> unregisteredResults = unregisteredTargets.stream().filter(Recipient::hasServiceId).map(t -> SendMessageResult.unregisteredFailure(new SignalServiceAddress(t.requireServiceId(), t.getE164().orNull()))).collect(Collectors.toList());
if (unregisteredResults.size() < unregisteredTargets.size()) {
Log.w(TAG, "There are " + (unregisteredTargets.size() - unregisteredResults.size()) + " targets that have no UUID! Cannot report a failure for them.");
}
allResults.addAll(unregisteredResults);
}
return allResults;
}
use of org.whispersystems.signalservice.api.push.SignalServiceAddress in project Signal-Android by WhisperSystems.
the class SignalServiceAddressProtobufSerializer method fromProtobuf.
public static SignalServiceAddress fromProtobuf(AddressProto addressProto) {
ServiceId serviceId = ServiceId.parseOrThrow(addressProto.getUuid().toByteArray());
Optional<String> number = addressProto.hasE164() ? Optional.of(addressProto.getE164()) : Optional.absent();
return new SignalServiceAddress(serviceId, number);
}
use of org.whispersystems.signalservice.api.push.SignalServiceAddress in project Signal-Android by WhisperSystems.
the class SignalServiceMessageReceiver method retrieveMessages.
public List<SignalServiceEnvelope> retrieveMessages(MessageReceivedCallback callback) throws IOException {
List<SignalServiceEnvelope> results = new LinkedList<>();
SignalServiceMessagesResult messageResult = socket.getMessages();
for (SignalServiceEnvelopeEntity entity : messageResult.getEnvelopes()) {
SignalServiceEnvelope envelope;
if (entity.hasSource() && entity.getSourceDevice() > 0) {
SignalServiceAddress address = new SignalServiceAddress(ACI.parseOrThrow(entity.getSourceUuid()), entity.getSourceE164());
envelope = new SignalServiceEnvelope(entity.getType(), Optional.of(address), entity.getSourceDevice(), entity.getTimestamp(), entity.getMessage(), entity.getContent(), entity.getServerTimestamp(), messageResult.getServerDeliveredTimestamp(), entity.getServerUuid());
} else {
envelope = new SignalServiceEnvelope(entity.getType(), entity.getTimestamp(), entity.getMessage(), entity.getContent(), entity.getServerTimestamp(), messageResult.getServerDeliveredTimestamp(), entity.getServerUuid());
}
callback.onMessage(envelope);
results.add(envelope);
if (envelope.hasServerGuid()) {
socket.acknowledgeMessage(envelope.getServerGuid());
} else {
socket.acknowledgeMessage(entity.getSourceE164(), entity.getTimestamp());
}
}
return results;
}
use of org.whispersystems.signalservice.api.push.SignalServiceAddress in project Signal-Android by signalapp.
the class GroupV1MessageProcessor method handleGroupUpdate.
@Nullable
private static Long handleGroupUpdate(@NonNull Context context, @NonNull SignalServiceContent content, @NonNull SignalServiceGroup group, @NonNull GroupRecord groupRecord, boolean outgoing) {
GroupDatabase database = SignalDatabase.groups();
GroupId.V1 id = GroupId.v1orThrow(group.getGroupId());
Set<RecipientId> recordMembers = new HashSet<>(groupRecord.getMembers());
Set<RecipientId> messageMembers = new HashSet<>();
if (group.getMembers().isPresent()) {
for (SignalServiceAddress messageMember : group.getMembers().get()) {
messageMembers.add(Recipient.externalGV1Member(messageMember).getId());
}
}
Set<RecipientId> addedMembers = new HashSet<>(messageMembers);
addedMembers.removeAll(recordMembers);
Set<RecipientId> missingMembers = new HashSet<>(recordMembers);
missingMembers.removeAll(messageMembers);
GroupContext.Builder builder = createGroupContext(group);
builder.setType(GroupContext.Type.UPDATE);
if (addedMembers.size() > 0) {
Set<RecipientId> unionMembers = new HashSet<>(recordMembers);
unionMembers.addAll(messageMembers);
database.updateMembers(id, new LinkedList<>(unionMembers));
builder.clearMembers();
builder.clearMembersE164();
for (RecipientId addedMember : addedMembers) {
Recipient recipient = Recipient.resolved(addedMember);
if (recipient.getE164().isPresent()) {
builder.addMembersE164(recipient.requireE164());
builder.addMembers(createMember(recipient.requireE164()));
}
}
} else {
builder.clearMembers();
builder.clearMembersE164();
}
if (missingMembers.size() > 0) {
// TODO We should tell added and missing about each-other.
}
if (group.getName().isPresent() || group.getAvatar().isPresent()) {
SignalServiceAttachment avatar = group.getAvatar().orNull();
database.update(id, group.getName().orNull(), avatar != null ? avatar.asPointer() : null);
}
if (group.getName().isPresent() && group.getName().get().equals(groupRecord.getTitle())) {
builder.clearName();
}
if (!groupRecord.isActive())
database.setActive(id, true);
return storeMessage(context, content, group, builder.build(), outgoing);
}
Aggregations