use of org.whispersystems.libsignal.util.guava.Optional in project Signal-Android by signalapp.
the class LinkPreviewRepository method fetchMetadata.
@NonNull
private RequestController fetchMetadata(@NonNull String url, Consumer<Metadata> callback) {
Call call = client.newCall(new Request.Builder().url(url).cacheControl(NO_CACHE).build());
call.enqueue(new okhttp3.Callback() {
@Override
public void onFailure(@NonNull Call call, @NonNull IOException e) {
Log.w(TAG, "Request failed.", e);
callback.accept(Metadata.empty());
}
@Override
public void onResponse(@NonNull Call call, @NonNull Response response) throws IOException {
if (!response.isSuccessful()) {
Log.w(TAG, "Non-successful response. Code: " + response.code());
callback.accept(Metadata.empty());
return;
} else if (response.body() == null) {
Log.w(TAG, "No response body.");
callback.accept(Metadata.empty());
return;
}
String body = OkHttpUtil.readAsString(response.body(), FAILSAFE_MAX_TEXT_SIZE);
OpenGraph openGraph = LinkPreviewUtil.parseOpenGraphFields(body);
Optional<String> title = openGraph.getTitle();
Optional<String> description = openGraph.getDescription();
Optional<String> imageUrl = openGraph.getImageUrl();
long date = openGraph.getDate();
if (imageUrl.isPresent() && !LinkPreviewUtil.isValidPreviewUrl(imageUrl.get())) {
Log.i(TAG, "Image URL was invalid or for a non-whitelisted domain. Skipping.");
imageUrl = Optional.absent();
}
callback.accept(new Metadata(title, description, date, imageUrl));
}
});
return new CallRequestController(call);
}
use of org.whispersystems.libsignal.util.guava.Optional in project Signal-Android by signalapp.
the class UnidentifiedAccessUtil method getAccessFor.
@WorkerThread
public static List<Optional<UnidentifiedAccessPair>> getAccessFor(@NonNull Context context, @NonNull List<Recipient> recipients, boolean log) {
byte[] ourUnidentifiedAccessKey = UnidentifiedAccess.deriveAccessKeyFrom(ProfileKeyUtil.getSelfProfileKey());
if (TextSecurePreferences.isUniversalUnidentifiedAccess(context)) {
ourUnidentifiedAccessKey = UNRESTRICTED_KEY;
}
List<Optional<UnidentifiedAccessPair>> access = new ArrayList<>(recipients.size());
Map<CertificateType, Integer> typeCounts = new HashMap<>();
for (Recipient recipient : recipients) {
byte[] theirUnidentifiedAccessKey = getTargetUnidentifiedAccessKey(recipient);
CertificateType certificateType = getUnidentifiedAccessCertificateType(recipient);
byte[] ourUnidentifiedAccessCertificate = SignalStore.certificateValues().getUnidentifiedAccessCertificate(certificateType);
int typeCount = Util.getOrDefault(typeCounts, certificateType, 0);
typeCount++;
typeCounts.put(certificateType, typeCount);
if (theirUnidentifiedAccessKey != null && ourUnidentifiedAccessCertificate != null) {
try {
access.add(Optional.of(new UnidentifiedAccessPair(new UnidentifiedAccess(theirUnidentifiedAccessKey, ourUnidentifiedAccessCertificate), new UnidentifiedAccess(ourUnidentifiedAccessKey, ourUnidentifiedAccessCertificate))));
} catch (InvalidCertificateException e) {
Log.w(TAG, e);
access.add(Optional.absent());
}
} else {
access.add(Optional.absent());
}
}
int unidentifiedCount = Stream.of(access).filter(Optional::isPresent).toList().size();
int otherCount = access.size() - unidentifiedCount;
if (log) {
Log.i(TAG, "Unidentified: " + unidentifiedCount + ", Other: " + otherCount + ". Types: " + typeCounts);
}
return access;
}
use of org.whispersystems.libsignal.util.guava.Optional in project Signal-Android by signalapp.
the class MessageContentProcessor method handleCallOfferMessage.
private void handleCallOfferMessage(@NonNull SignalServiceContent content, @NonNull OfferMessage message, @NonNull Optional<Long> smsMessageId, @NonNull Recipient senderRecipient) {
log(String.valueOf(content.getTimestamp()), "handleCallOfferMessage...");
if (smsMessageId.isPresent()) {
MessageDatabase database = SignalDatabase.sms();
database.markAsMissedCall(smsMessageId.get(), message.getType() == OfferMessage.Type.VIDEO_CALL);
} else {
RemotePeer remotePeer = new RemotePeer(senderRecipient.getId(), new CallId(message.getId()));
byte[] remoteIdentityKey = ApplicationDependencies.getProtocolStore().aci().identities().getIdentityRecord(senderRecipient.getId()).transform(record -> record.getIdentityKey().serialize()).orNull();
ApplicationDependencies.getSignalCallManager().receivedOffer(new WebRtcData.CallMetadata(remotePeer, content.getSenderDevice()), new WebRtcData.OfferMetadata(message.getOpaque(), message.getSdp(), message.getType()), new WebRtcData.ReceivedOfferMetadata(remoteIdentityKey, content.getServerReceivedTimestamp(), content.getServerDeliveredTimestamp(), content.getCallMessage().get().isMultiRing()));
}
}
use of org.whispersystems.libsignal.util.guava.Optional in project Signal-Android by signalapp.
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.libsignal.util.guava.Optional in project Signal-Android by signalapp.
the class ChunkedDataFetcher method fetchChunksWithUnknownTotalSize.
private RequestController fetchChunksWithUnknownTotalSize(@NonNull String url, @NonNull Callback callback) {
CompositeRequestController compositeController = new CompositeRequestController();
long chunkSize = new SecureRandom().nextInt(1024) + 1024;
Request request = new Request.Builder().url(url).cacheControl(NO_CACHE).addHeader("Range", "bytes=0-" + (chunkSize - 1)).addHeader("Accept-Encoding", "identity").build();
Call firstChunkCall = client.newCall(request);
compositeController.addController(new CallRequestController(firstChunkCall));
firstChunkCall.enqueue(new okhttp3.Callback() {
@Override
public void onFailure(@NonNull Call call, @NonNull IOException e) {
if (!compositeController.isCanceled()) {
callback.onFailure(e);
compositeController.cancel();
}
}
@Override
public void onResponse(@NonNull Call call, @NonNull Response response) {
String contentRange = response.header("Content-Range");
if (!response.isSuccessful()) {
Log.w(TAG, "Non-successful response code: " + response.code());
callback.onFailure(new IOException("Non-successful response code: " + response.code()));
compositeController.cancel();
if (response.body() != null)
response.body().close();
return;
}
if (TextUtils.isEmpty(contentRange)) {
Log.w(TAG, "Missing Content-Range header.");
callback.onFailure(new IOException("Missing Content-Length header."));
compositeController.cancel();
if (response.body() != null)
response.body().close();
return;
}
if (response.body() == null) {
Log.w(TAG, "Missing body.");
callback.onFailure(new IOException("Missing body on initial request."));
compositeController.cancel();
return;
}
Optional<Long> contentLength = parseLengthFromContentRange(contentRange);
if (!contentLength.isPresent()) {
Log.w(TAG, "Unable to parse length from Content-Range.");
callback.onFailure(new IOException("Unable to get parse length from Content-Range."));
compositeController.cancel();
return;
}
if (chunkSize >= contentLength.get()) {
try {
callback.onSuccess(response.body().byteStream());
} catch (IOException e) {
callback.onFailure(e);
compositeController.cancel();
}
} else {
InputStream stream = ContentLengthInputStream.obtain(response.body().byteStream(), chunkSize);
fetchChunks(url, contentLength.get(), Optional.of(new Pair<>(stream, chunkSize)), compositeController, callback);
}
}
});
return compositeController;
}
Aggregations