Respect the 206 paged response from the server group logs endpoint.
Prevent the deduplicate message logic firing and log it if it does.master
parent
07b0d8cf6e
commit
054c705fe2
|
@ -372,6 +372,7 @@ public final class GroupsV2StateProcessor {
|
||||||
Log.d(TAG, "Skipping profile key changes only update message");
|
Log.d(TAG, "Skipping profile key changes only update message");
|
||||||
} else {
|
} else {
|
||||||
storeMessage(GroupProtoUtil.createDecryptedGroupV2Context(masterKey, new GroupMutation(previousGroupState, entry.getChange(), entry.getGroup()), null), timestamp);
|
storeMessage(GroupProtoUtil.createDecryptedGroupV2Context(masterKey, new GroupMutation(previousGroupState, entry.getChange(), entry.getGroup()), null), timestamp);
|
||||||
|
timestamp++;
|
||||||
}
|
}
|
||||||
previousGroupState = entry.getGroup();
|
previousGroupState = entry.getGroup();
|
||||||
}
|
}
|
||||||
|
@ -476,7 +477,9 @@ public final class GroupsV2StateProcessor {
|
||||||
IncomingTextMessage incoming = new IncomingTextMessage(sender, -1, timestamp, timestamp, "", Optional.of(groupId), 0, false);
|
IncomingTextMessage incoming = new IncomingTextMessage(sender, -1, timestamp, timestamp, "", Optional.of(groupId), 0, false);
|
||||||
IncomingGroupUpdateMessage groupMessage = new IncomingGroupUpdateMessage(incoming, decryptedGroupV2Context);
|
IncomingGroupUpdateMessage groupMessage = new IncomingGroupUpdateMessage(incoming, decryptedGroupV2Context);
|
||||||
|
|
||||||
smsDatabase.insertMessageInbox(groupMessage);
|
if (!smsDatabase.insertMessageInbox(groupMessage).isPresent()) {
|
||||||
|
Log.w(TAG, "Could not insert update message");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -27,6 +27,7 @@ import java.io.IOException;
|
||||||
import java.security.SecureRandom;
|
import java.security.SecureRandom;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
|
import java.util.LinkedList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
|
|
||||||
|
@ -97,9 +98,19 @@ public final class GroupsV2Api {
|
||||||
GroupsV2AuthorizationString authorization)
|
GroupsV2AuthorizationString authorization)
|
||||||
throws IOException, InvalidGroupStateException, VerificationFailedException
|
throws IOException, InvalidGroupStateException, VerificationFailedException
|
||||||
{
|
{
|
||||||
GroupChanges group = socket.getGroupsV2GroupHistory(fromRevision, authorization);
|
List<GroupChanges.GroupChangeState> changesList = new LinkedList<>();
|
||||||
|
PushServiceSocket.GroupHistory group;
|
||||||
|
|
||||||
|
do {
|
||||||
|
group = socket.getGroupsV2GroupHistory(fromRevision, authorization);
|
||||||
|
|
||||||
|
changesList.addAll(group.getGroupChanges().getGroupChangesList());
|
||||||
|
|
||||||
|
if (group.hasMore()) {
|
||||||
|
fromRevision = group.getNextPageStartGroupRevision();
|
||||||
|
}
|
||||||
|
} while (group.hasMore());
|
||||||
|
|
||||||
List<GroupChanges.GroupChangeState> changesList = group.getGroupChangesList();
|
|
||||||
ArrayList<DecryptedGroupHistoryEntry> result = new ArrayList<>(changesList.size());
|
ArrayList<DecryptedGroupHistoryEntry> result = new ArrayList<>(changesList.size());
|
||||||
GroupsV2Operations.GroupOperations groupOperations = groupsOperations.forGroup(groupSecretParams);
|
GroupsV2Operations.GroupOperations groupOperations = groupsOperations.forGroup(groupSecretParams);
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,49 @@
|
||||||
|
package org.whispersystems.signalservice.internal.push;
|
||||||
|
|
||||||
|
import org.whispersystems.libsignal.util.guava.Optional;
|
||||||
|
|
||||||
|
import java.util.regex.Matcher;
|
||||||
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
|
public final class ContentRange {
|
||||||
|
private static final Pattern PATTERN = Pattern.compile("versions (\\d+)-(\\d+)\\/(\\d+)");
|
||||||
|
|
||||||
|
private final int rangeStart;
|
||||||
|
private final int rangeEnd;
|
||||||
|
private final int totalSize;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parses a content range header.
|
||||||
|
*/
|
||||||
|
public static Optional<ContentRange> parse(String header) {
|
||||||
|
if (header != null) {
|
||||||
|
Matcher matcher = PATTERN.matcher(header);
|
||||||
|
|
||||||
|
if (matcher.matches()) {
|
||||||
|
return Optional.of(new ContentRange(Integer.parseInt(matcher.group(1)),
|
||||||
|
Integer.parseInt(matcher.group(2)),
|
||||||
|
Integer.parseInt(matcher.group(3))));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return Optional.absent();
|
||||||
|
}
|
||||||
|
|
||||||
|
private ContentRange(int rangeStart, int rangeEnd, int totalSize) {
|
||||||
|
this.rangeStart = rangeStart;
|
||||||
|
this.rangeEnd = rangeEnd;
|
||||||
|
this.totalSize = totalSize;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getRangeStart() {
|
||||||
|
return rangeStart;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getRangeEnd() {
|
||||||
|
return rangeEnd;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getTotalSize() {
|
||||||
|
return totalSize;
|
||||||
|
}
|
||||||
|
}
|
|
@ -1585,6 +1585,12 @@ public class PushServiceSocket {
|
||||||
|
|
||||||
private ResponseBody makeStorageRequest(String authorization, String path, String method, RequestBody body, ResponseCodeHandler responseCodeHandler)
|
private ResponseBody makeStorageRequest(String authorization, String path, String method, RequestBody body, ResponseCodeHandler responseCodeHandler)
|
||||||
throws PushNetworkException, NonSuccessfulResponseCodeException
|
throws PushNetworkException, NonSuccessfulResponseCodeException
|
||||||
|
{
|
||||||
|
return makeStorageRequestResponse(authorization, path, method, body, responseCodeHandler).body();
|
||||||
|
}
|
||||||
|
|
||||||
|
private Response makeStorageRequestResponse(String authorization, String path, String method, RequestBody body, ResponseCodeHandler responseCodeHandler)
|
||||||
|
throws PushNetworkException, NonSuccessfulResponseCodeException
|
||||||
{
|
{
|
||||||
ConnectionHolder connectionHolder = getRandom(storageClients, random);
|
ConnectionHolder connectionHolder = getRandom(storageClients, random);
|
||||||
OkHttpClient okHttpClient = connectionHolder.getClient()
|
OkHttpClient okHttpClient = connectionHolder.getClient()
|
||||||
|
@ -1618,7 +1624,7 @@ public class PushServiceSocket {
|
||||||
response = call.execute();
|
response = call.execute();
|
||||||
|
|
||||||
if (response.isSuccessful() && response.code() != 204) {
|
if (response.isSuccessful() && response.code() != 204) {
|
||||||
return response.body();
|
return response;
|
||||||
}
|
}
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
throw new PushNetworkException(e);
|
throw new PushNetworkException(e);
|
||||||
|
@ -1745,6 +1751,9 @@ public class PushServiceSocket {
|
||||||
* Converts {@link IOException} on body byte reading to {@link PushNetworkException}.
|
* Converts {@link IOException} on body byte reading to {@link PushNetworkException}.
|
||||||
*/
|
*/
|
||||||
private static byte[] readBodyBytes(ResponseBody response) throws PushNetworkException {
|
private static byte[] readBodyBytes(ResponseBody response) throws PushNetworkException {
|
||||||
|
if (response == null) {
|
||||||
|
throw new PushNetworkException("No body!");
|
||||||
|
}
|
||||||
try {
|
try {
|
||||||
return response.bytes();
|
return response.bytes();
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
|
@ -1977,16 +1986,31 @@ public class PushServiceSocket {
|
||||||
return GroupChange.parseFrom(readBodyBytes(response));
|
return GroupChange.parseFrom(readBodyBytes(response));
|
||||||
}
|
}
|
||||||
|
|
||||||
public GroupChanges getGroupsV2GroupHistory(int fromVersion, GroupsV2AuthorizationString authorization)
|
public GroupHistory getGroupsV2GroupHistory(int fromVersion, GroupsV2AuthorizationString authorization)
|
||||||
throws NonSuccessfulResponseCodeException, PushNetworkException, InvalidProtocolBufferException
|
throws NonSuccessfulResponseCodeException, PushNetworkException, InvalidProtocolBufferException
|
||||||
{
|
{
|
||||||
ResponseBody response = makeStorageRequest(authorization.toString(),
|
Response response = makeStorageRequestResponse(authorization.toString(),
|
||||||
String.format(Locale.US, GROUPSV2_GROUP_CHANGES, fromVersion),
|
String.format(Locale.US, GROUPSV2_GROUP_CHANGES, fromVersion),
|
||||||
"GET",
|
"GET",
|
||||||
null,
|
null,
|
||||||
GROUPS_V2_GET_LOGS_HANDLER);
|
GROUPS_V2_GET_LOGS_HANDLER);
|
||||||
|
|
||||||
return GroupChanges.parseFrom(readBodyBytes(response));
|
GroupChanges groupChanges = GroupChanges.parseFrom(readBodyBytes(response.body()));
|
||||||
|
|
||||||
|
if (response.code() == 206) {
|
||||||
|
String contentRangeHeader = response.header("Content-Range");
|
||||||
|
Optional<ContentRange> contentRange = ContentRange.parse(contentRangeHeader);
|
||||||
|
|
||||||
|
if (contentRange.isPresent()) {
|
||||||
|
Log.i(TAG, "Additional logs for group: " + contentRangeHeader);
|
||||||
|
return new GroupHistory(groupChanges, contentRange);
|
||||||
|
} else {
|
||||||
|
Log.w(TAG, "Unable to parse Content-Range header: " + contentRangeHeader);
|
||||||
|
throw new NonSuccessfulResponseCodeException("Unable to parse content range header on 206");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return new GroupHistory(groupChanges, Optional.absent());
|
||||||
}
|
}
|
||||||
|
|
||||||
public GroupJoinInfo getGroupJoinInfo(Optional<byte[]> groupLinkPassword, GroupsV2AuthorizationString authorization)
|
public GroupJoinInfo getGroupJoinInfo(Optional<byte[]> groupLinkPassword, GroupsV2AuthorizationString authorization)
|
||||||
|
@ -2002,6 +2026,31 @@ public class PushServiceSocket {
|
||||||
return GroupJoinInfo.parseFrom(readBodyBytes(response));
|
return GroupJoinInfo.parseFrom(readBodyBytes(response));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static final class GroupHistory {
|
||||||
|
private final GroupChanges groupChanges;
|
||||||
|
private final Optional<ContentRange> contentRange;
|
||||||
|
|
||||||
|
public GroupHistory(GroupChanges groupChanges, Optional<ContentRange> contentRange) {
|
||||||
|
this.groupChanges = groupChanges;
|
||||||
|
this.contentRange = contentRange;
|
||||||
|
}
|
||||||
|
|
||||||
|
public GroupChanges getGroupChanges() {
|
||||||
|
return groupChanges;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean hasMore() {
|
||||||
|
return contentRange.isPresent();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Valid iff {@link #hasMore()}.
|
||||||
|
*/
|
||||||
|
public int getNextPageStartGroupRevision() {
|
||||||
|
return contentRange.get().getRangeEnd() + 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private final class ResumeInfo {
|
private final class ResumeInfo {
|
||||||
private final String contentRange;
|
private final String contentRange;
|
||||||
private final long contentStart;
|
private final long contentStart;
|
||||||
|
|
|
@ -0,0 +1,49 @@
|
||||||
|
package org.whispersystems.signalservice.internal.push;
|
||||||
|
|
||||||
|
import org.junit.Test;
|
||||||
|
import org.junit.runner.RunWith;
|
||||||
|
import org.junit.runners.Parameterized;
|
||||||
|
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.Collection;
|
||||||
|
|
||||||
|
import static org.junit.Assert.assertEquals;
|
||||||
|
|
||||||
|
@RunWith(Parameterized.class)
|
||||||
|
public final class ContentRange_parse_Test {
|
||||||
|
|
||||||
|
@Parameterized.Parameter(0)
|
||||||
|
public String input;
|
||||||
|
|
||||||
|
@Parameterized.Parameter(1)
|
||||||
|
public int expectedRangeStart;
|
||||||
|
|
||||||
|
@Parameterized.Parameter(2)
|
||||||
|
public int expectedRangeEnd;
|
||||||
|
|
||||||
|
@Parameterized.Parameter(3)
|
||||||
|
public int expectedSize;
|
||||||
|
|
||||||
|
@Parameterized.Parameters(name = "Content-Range: \"{0}\"")
|
||||||
|
public static Collection<Object[]> data() {
|
||||||
|
return Arrays.asList(new Object[][]{
|
||||||
|
{ "versions 1-2/3", 1, 2, 3 },
|
||||||
|
{ "versions 23-45/67", 23, 45, 67 },
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void rangeStart() {
|
||||||
|
assertEquals(expectedRangeStart, ContentRange.parse(input).get().getRangeStart());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void rangeEnd() {
|
||||||
|
assertEquals(expectedRangeEnd, ContentRange.parse(input).get().getRangeEnd());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void totalSize() {
|
||||||
|
assertEquals(expectedSize, ContentRange.parse(input).get().getTotalSize());
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,34 @@
|
||||||
|
package org.whispersystems.signalservice.internal.push;
|
||||||
|
|
||||||
|
import org.junit.Test;
|
||||||
|
import org.junit.runner.RunWith;
|
||||||
|
import org.junit.runners.Parameterized;
|
||||||
|
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.Collection;
|
||||||
|
|
||||||
|
import static org.junit.Assert.assertFalse;
|
||||||
|
|
||||||
|
@RunWith(Parameterized.class)
|
||||||
|
public final class ContentRange_parse_withInvalidStrings_Test {
|
||||||
|
|
||||||
|
@Parameterized.Parameter(0)
|
||||||
|
public String input;
|
||||||
|
|
||||||
|
@Parameterized.Parameters(name = "Content-Range: \"{0}\"")
|
||||||
|
public static Collection<Object[]> data() {
|
||||||
|
return Arrays.asList(new Object[][]{
|
||||||
|
{ null },
|
||||||
|
{ "" },
|
||||||
|
{ "23-45/67" },
|
||||||
|
{ "ersions 23-45/67" },
|
||||||
|
{ "versions 23-45" },
|
||||||
|
{ "versions a-b/c" }
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void parse_should_be_absent() {
|
||||||
|
assertFalse(ContentRange.parse(input).isPresent());
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue