Add initial support for Group Calling.

This commit is contained in:
Cody Henthorne
2020-11-11 15:11:03 -05:00
parent 696fffb603
commit b1f6786392
53 changed files with 1887 additions and 130 deletions

View File

@@ -34,6 +34,7 @@ import org.whispersystems.signalservice.api.messages.SignalServiceGroupV2;
import org.whispersystems.signalservice.api.messages.SignalServiceReceiptMessage;
import org.whispersystems.signalservice.api.messages.SignalServiceTypingMessage;
import org.whispersystems.signalservice.api.messages.calls.AnswerMessage;
import org.whispersystems.signalservice.api.messages.calls.CallingResponse;
import org.whispersystems.signalservice.api.messages.calls.IceUpdateMessage;
import org.whispersystems.signalservice.api.messages.calls.OfferMessage;
import org.whispersystems.signalservice.api.messages.calls.SignalServiceCallMessage;
@@ -89,7 +90,6 @@ import org.whispersystems.util.Base64;
import java.io.IOException;
import java.io.InputStream;
import java.security.SecureRandom;
import java.sql.Time;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
@@ -239,6 +239,20 @@ public class SignalServiceMessageSender {
sendMessage(recipient, getTargetUnidentifiedAccess(unidentifiedAccess), System.currentTimeMillis(), content, false, null);
}
/**
* Send an http request on behalf of the calling infrastructure.
*
* @param requestId Request identifier
* @param url Fully qualified URL to request
* @param httpMethod Http method to use (e.g., "GET", "POST")
* @param headers Optional list of headers to send with request
* @param body Optional body to send with request
* @return
*/
public CallingResponse makeCallingRequest(long requestId, String url, String httpMethod, List<Pair<String, String>> headers, byte[] body) {
return socket.makeCallingRequest(requestId, url, httpMethod, headers, body);
}
/**
* Send a message to a single recipient.
*
@@ -773,6 +787,8 @@ public class SignalServiceMessageSender {
}
} else if (callMessage.getBusyMessage().isPresent()) {
builder.setBusy(CallMessage.Busy.newBuilder().setId(callMessage.getBusyMessage().get().getId()));
} else if (callMessage.getOpaqueMessage().isPresent()) {
builder.setOpaque(CallMessage.Opaque.newBuilder().setData(ByteString.copyFrom(callMessage.getOpaqueMessage().get().getOpaque())));
}
builder.setMultiRing(callMessage.isMultiRing());

View File

@@ -7,6 +7,7 @@ import org.signal.storageservice.protos.groups.Group;
import org.signal.storageservice.protos.groups.GroupAttributeBlob;
import org.signal.storageservice.protos.groups.GroupChange;
import org.signal.storageservice.protos.groups.GroupChanges;
import org.signal.storageservice.protos.groups.GroupExternalCredential;
import org.signal.storageservice.protos.groups.GroupJoinInfo;
import org.signal.storageservice.protos.groups.local.DecryptedGroup;
import org.signal.storageservice.protos.groups.local.DecryptedGroupChange;
@@ -166,6 +167,12 @@ public final class GroupsV2Api {
return socket.patchGroupsV2Group(groupChange, authorization.toString(), groupLinkPassword);
}
public GroupExternalCredential getGroupExternalCredential(GroupsV2AuthorizationString authorization)
throws IOException
{
return socket.getGroupExternalCredential(authorization);
}
private static HashMap<Integer, AuthCredentialResponse> parseCredentialResponse(CredentialResponse credentialResponse)
throws IOException
{

View File

@@ -23,6 +23,7 @@ import org.whispersystems.signalservice.api.messages.calls.BusyMessage;
import org.whispersystems.signalservice.api.messages.calls.HangupMessage;
import org.whispersystems.signalservice.api.messages.calls.IceUpdateMessage;
import org.whispersystems.signalservice.api.messages.calls.OfferMessage;
import org.whispersystems.signalservice.api.messages.calls.OpaqueMessage;
import org.whispersystems.signalservice.api.messages.calls.SignalServiceCallMessage;
import org.whispersystems.signalservice.api.messages.multidevice.BlockedListMessage;
import org.whispersystems.signalservice.api.messages.multidevice.ConfigurationMessage;
@@ -616,6 +617,9 @@ public final class SignalServiceContent {
} else if (content.hasBusy()) {
SignalServiceProtos.CallMessage.Busy busy = content.getBusy();
return SignalServiceCallMessage.forBusy(new BusyMessage(busy.getId()), isMultiRing, destinationDeviceId);
} else if (content.hasOpaque()) {
SignalServiceProtos.CallMessage.Opaque opaque = content.getOpaque();
return SignalServiceCallMessage.forOpaque(new OpaqueMessage(opaque.getData().toByteArray()), isMultiRing, destinationDeviceId);
}
return SignalServiceCallMessage.empty();

View File

@@ -0,0 +1,48 @@
package org.whispersystems.signalservice.api.messages.calls;
/**
* Encapsulate the response to an http request on behalf of ringrtc.
*/
public abstract class CallingResponse {
private final long requestId;
CallingResponse(long requestId) {
this.requestId = requestId;
}
public long getRequestId() {
return requestId;
}
public static class Success extends CallingResponse {
private final int responseStatus;
private final byte[] responseBody;
public Success(long requestId, int responseStatus, byte[] responseBody) {
super(requestId);
this.responseStatus = responseStatus;
this.responseBody = responseBody;
}
public int getResponseStatus() {
return responseStatus;
}
public byte[] getResponseBody() {
return responseBody;
}
}
public static class Error extends CallingResponse {
private final Throwable throwable;
public Error(long requestId, Throwable throwable) {
super(requestId);
this.throwable = throwable;
}
public Throwable getThrowable() {
return throwable;
}
}
}

View File

@@ -0,0 +1,14 @@
package org.whispersystems.signalservice.api.messages.calls;
public class OpaqueMessage {
private final byte[] opaque;
public OpaqueMessage(byte[] opaque) {
this.opaque = opaque;
}
public byte[] getOpaque() {
return opaque;
}
}

View File

@@ -12,6 +12,7 @@ public class SignalServiceCallMessage {
private final Optional<HangupMessage> hangupMessage;
private final Optional<BusyMessage> busyMessage;
private final Optional<List<IceUpdateMessage>> iceUpdateMessages;
private final Optional<OpaqueMessage> opaqueMessage;
private final Optional<Integer> destinationDeviceId;
private final boolean isMultiRing;
@@ -20,6 +21,7 @@ public class SignalServiceCallMessage {
Optional<List<IceUpdateMessage>> iceUpdateMessages,
Optional<HangupMessage> hangupMessage,
Optional<BusyMessage> busyMessage,
Optional<OpaqueMessage> opaqueMessage,
boolean isMultiRing,
Optional<Integer> destinationDeviceId)
{
@@ -28,6 +30,7 @@ public class SignalServiceCallMessage {
this.iceUpdateMessages = iceUpdateMessages;
this.hangupMessage = hangupMessage;
this.busyMessage = busyMessage;
this.opaqueMessage = opaqueMessage;
this.isMultiRing = isMultiRing;
this.destinationDeviceId = destinationDeviceId;
}
@@ -38,6 +41,7 @@ public class SignalServiceCallMessage {
Optional.absent(),
Optional.absent(),
Optional.absent(),
Optional.absent(),
isMultiRing,
Optional.fromNullable(destinationDeviceId));
}
@@ -48,6 +52,7 @@ public class SignalServiceCallMessage {
Optional.absent(),
Optional.absent(),
Optional.absent(),
Optional.absent(),
isMultiRing,
Optional.fromNullable(destinationDeviceId));
}
@@ -58,6 +63,7 @@ public class SignalServiceCallMessage {
Optional.of(iceUpdateMessages),
Optional.absent(),
Optional.absent(),
Optional.absent(),
isMultiRing,
Optional.fromNullable(destinationDeviceId));
}
@@ -71,6 +77,7 @@ public class SignalServiceCallMessage {
Optional.of(iceUpdateMessages),
Optional.absent(),
Optional.absent(),
Optional.absent(),
isMultiRing,
Optional.fromNullable(destinationDeviceId));
}
@@ -81,6 +88,7 @@ public class SignalServiceCallMessage {
Optional.absent(),
Optional.of(hangupMessage),
Optional.absent(),
Optional.absent(),
isMultiRing,
Optional.fromNullable(destinationDeviceId));
}
@@ -91,6 +99,18 @@ public class SignalServiceCallMessage {
Optional.absent(),
Optional.absent(),
Optional.of(busyMessage),
Optional.absent(),
isMultiRing,
Optional.fromNullable(destinationDeviceId));
}
public static SignalServiceCallMessage forOpaque(OpaqueMessage opaqueMessage, boolean isMultiRing, Integer destinationDeviceId) {
return new SignalServiceCallMessage(Optional.absent(),
Optional.absent(),
Optional.absent(),
Optional.absent(),
Optional.absent(),
Optional.of(opaqueMessage),
isMultiRing,
Optional.fromNullable(destinationDeviceId));
}
@@ -102,7 +122,7 @@ public class SignalServiceCallMessage {
Optional.absent(),
Optional.absent(),
Optional.absent(),
false,
Optional.absent(), false,
Optional.absent());
}
@@ -126,6 +146,10 @@ public class SignalServiceCallMessage {
return busyMessage;
}
public Optional<OpaqueMessage> getOpaqueMessage() {
return opaqueMessage;
}
public boolean isMultiRing() {
return isMultiRing;
}

View File

@@ -15,6 +15,7 @@ import org.signal.storageservice.protos.groups.AvatarUploadAttributes;
import org.signal.storageservice.protos.groups.Group;
import org.signal.storageservice.protos.groups.GroupChange;
import org.signal.storageservice.protos.groups.GroupChanges;
import org.signal.storageservice.protos.groups.GroupExternalCredential;
import org.signal.storageservice.protos.groups.GroupJoinInfo;
import org.signal.zkgroup.VerificationFailedException;
import org.signal.zkgroup.profiles.ClientZkProfileOperations;
@@ -38,6 +39,7 @@ import org.whispersystems.signalservice.api.groupsv2.CredentialResponse;
import org.whispersystems.signalservice.api.groupsv2.GroupsV2AuthorizationString;
import org.whispersystems.signalservice.api.messages.SignalServiceAttachment.ProgressListener;
import org.whispersystems.signalservice.api.messages.SignalServiceAttachmentRemoteId;
import org.whispersystems.signalservice.api.messages.calls.CallingResponse;
import org.whispersystems.signalservice.api.messages.calls.TurnServerInfo;
import org.whispersystems.signalservice.api.messages.multidevice.DeviceInfo;
import org.whispersystems.signalservice.api.profiles.ProfileAndCredential;
@@ -204,6 +206,7 @@ public class PushServiceSocket {
private static final String GROUPSV2_GROUP_CHANGES = "/v1/groups/logs/%s";
private static final String GROUPSV2_AVATAR_REQUEST = "/v1/groups/avatar/form";
private static final String GROUPSV2_GROUP_JOIN = "/v1/groups/join/%s";
private static final String GROUPSV2_TOKEN = "/v1/groups/token";
private static final String SERVER_DELIVERED_TIMESTAMP_HEADER = "X-Signal-Timestamp";
@@ -1665,6 +1668,42 @@ public class PushServiceSocket {
throw new NonSuccessfulResponseCodeException("Response: " + response);
}
public CallingResponse makeCallingRequest(long requestId, String url, String httpMethod, List<Pair<String, String>> headers, byte[] body) {
ConnectionHolder connectionHolder = getRandom(serviceClients, random);
OkHttpClient okHttpClient = connectionHolder.getClient()
.newBuilder()
.followRedirects(true)
.connectTimeout(soTimeoutMillis, TimeUnit.MILLISECONDS)
.readTimeout(soTimeoutMillis, TimeUnit.MILLISECONDS)
.build();
RequestBody requestBody = body != null ? RequestBody.create(null, body) : null;
Request.Builder builder = new Request.Builder()
.url(url)
.method(httpMethod, requestBody);
if (headers != null) {
for (Pair<String, String> header : headers) {
builder.addHeader(header.first(), header.second());
}
}
Call call = okHttpClient.newCall(builder.build());
try {
Response response = call.execute();
int responseStatus = response.code();
byte[] responseBody = response.body() != null ? response.body().bytes() : new byte[0];
return new CallingResponse.Success(requestId, responseStatus, responseBody);
} catch (IOException e) {
Log.w(TAG, "Exception during ringrtc http call.", e);
return new CallingResponse.Error(requestId, e);
}
}
private ServiceConnectionHolder[] createServiceConnectionHolders(SignalUrl[] urls,
List<Interceptor> interceptors,
Optional<Dns> dns)
@@ -2037,6 +2076,18 @@ public class PushServiceSocket {
return GroupJoinInfo.parseFrom(readBodyBytes(response));
}
public GroupExternalCredential getGroupExternalCredential(GroupsV2AuthorizationString authorization)
throws NonSuccessfulResponseCodeException, PushNetworkException, InvalidProtocolBufferException
{
ResponseBody response = makeStorageRequest(authorization.toString(),
GROUPSV2_TOKEN,
"GET",
null,
NO_HANDLER);
return GroupExternalCredential.parseFrom(readBodyBytes(response));
}
public static final class GroupHistory {
private final GroupChanges groupChanges;
private final Optional<ContentRange> contentRange;

View File

@@ -210,3 +210,7 @@ message GroupJoinInfo {
uint32 revision = 6;
bool pendingAdminApproval = 7;
}
message GroupExternalCredential {
string token = 1;
}

View File

@@ -92,6 +92,9 @@ message CallMessage {
optional uint32 deviceId = 3;
}
message Opaque {
optional bytes data = 1;
}
optional Offer offer = 1;
optional Answer answer = 2;
@@ -102,6 +105,7 @@ message CallMessage {
optional Hangup hangup = 7;
optional bool multiRing = 8;
optional uint32 destinationDeviceId = 9;
optional Opaque opaque = 10;
}
message DataMessage {