From 2dfe9eea94b05619d79a02d6292562918a072e29 Mon Sep 17 00:00:00 2001 From: Moxie Marlinspike Date: Mon, 9 Jan 2017 11:50:56 -0800 Subject: [PATCH] Support for video account attributes // FREEBIE --- .../controllers/AccountController.java | 2 ++ .../controllers/FederationControllerV1.java | 2 +- .../entities/AccountAttributes.java | 12 ++++++++++-- .../textsecuregcm/entities/ClientContact.java | 15 ++++++++++++++- .../federation/NonLimitedAccount.java | 2 +- .../textsecuregcm/storage/Account.java | 12 +++++++++++- .../textsecuregcm/storage/AccountsManager.java | 2 +- .../textsecuregcm/storage/Device.java | 14 +++++++++++++- .../textsecuregcm/storage/DirectoryManager.java | 16 ++++++++++------ .../textsecuregcm/workers/DirectoryUpdater.java | 2 +- .../tests/controllers/DeviceControllerTest.java | 2 +- .../controllers/FederatedControllerTest.java | 6 +++--- .../tests/controllers/MessageControllerTest.java | 8 ++++---- .../tests/controllers/ReceiptControllerTest.java | 6 +++--- .../tests/entities/ClientContactTest.java | 13 +++++++++---- .../textsecuregcm/tests/entities/PreKeyTest.java | 2 +- .../resources/fixtures/contact.relay.video.json | 6 ++++++ 17 files changed, 91 insertions(+), 31 deletions(-) create mode 100644 src/test/resources/fixtures/contact.relay.video.json diff --git a/src/main/java/org/whispersystems/textsecuregcm/controllers/AccountController.java b/src/main/java/org/whispersystems/textsecuregcm/controllers/AccountController.java index 7f5a35838..d8793b2ec 100644 --- a/src/main/java/org/whispersystems/textsecuregcm/controllers/AccountController.java +++ b/src/main/java/org/whispersystems/textsecuregcm/controllers/AccountController.java @@ -307,6 +307,7 @@ public class AccountController { device.setName(attributes.getName()); device.setLastSeen(Util.todayInMillis()); device.setVoiceSupported(attributes.getVoice()); + device.setVideoSupported(attributes.getVideo()); device.setRegistrationId(attributes.getRegistrationId()); device.setSignalingKey(attributes.getSignalingKey()); device.setUserAgent(userAgent); @@ -332,6 +333,7 @@ public class AccountController { device.setRegistrationId(accountAttributes.getRegistrationId()); device.setName(accountAttributes.getName()); device.setVoiceSupported(accountAttributes.getVoice()); + device.setVideoSupported(accountAttributes.getVideo()); device.setCreated(System.currentTimeMillis()); device.setLastSeen(Util.todayInMillis()); device.setUserAgent(userAgent); diff --git a/src/main/java/org/whispersystems/textsecuregcm/controllers/FederationControllerV1.java b/src/main/java/org/whispersystems/textsecuregcm/controllers/FederationControllerV1.java index e84967683..7544b89d9 100644 --- a/src/main/java/org/whispersystems/textsecuregcm/controllers/FederationControllerV1.java +++ b/src/main/java/org/whispersystems/textsecuregcm/controllers/FederationControllerV1.java @@ -150,7 +150,7 @@ public class FederationControllerV1 extends FederationController { for (Account account : accountList) { byte[] token = Util.getContactToken(account.getNumber()); - ClientContact clientContact = new ClientContact(token, null, account.isVoiceSupported()); + ClientContact clientContact = new ClientContact(token, null, account.isVoiceSupported(), account.isVideoSupported()); if (!account.isActive()) { clientContact.setInactive(true); diff --git a/src/main/java/org/whispersystems/textsecuregcm/entities/AccountAttributes.java b/src/main/java/org/whispersystems/textsecuregcm/entities/AccountAttributes.java index e2e7cbb91..dd7dd5ec7 100644 --- a/src/main/java/org/whispersystems/textsecuregcm/entities/AccountAttributes.java +++ b/src/main/java/org/whispersystems/textsecuregcm/entities/AccountAttributes.java @@ -40,20 +40,24 @@ public class AccountAttributes { @JsonProperty private boolean voice; + @JsonProperty + private boolean video; + public AccountAttributes() {} @VisibleForTesting public AccountAttributes(String signalingKey, boolean fetchesMessages, int registrationId) { - this(signalingKey, fetchesMessages, registrationId, null, false); + this(signalingKey, fetchesMessages, registrationId, null, false, false); } @VisibleForTesting - public AccountAttributes(String signalingKey, boolean fetchesMessages, int registrationId, String name, boolean voice) { + public AccountAttributes(String signalingKey, boolean fetchesMessages, int registrationId, String name, boolean voice, boolean video) { this.signalingKey = signalingKey; this.fetchesMessages = fetchesMessages; this.registrationId = registrationId; this.name = name; this.voice = voice; + this.video = video; } public String getSignalingKey() { @@ -76,4 +80,8 @@ public class AccountAttributes { return voice; } + public boolean getVideo() { + return video; + } + } diff --git a/src/main/java/org/whispersystems/textsecuregcm/entities/ClientContact.java b/src/main/java/org/whispersystems/textsecuregcm/entities/ClientContact.java index 7beadee92..4e1b8aa5c 100644 --- a/src/main/java/org/whispersystems/textsecuregcm/entities/ClientContact.java +++ b/src/main/java/org/whispersystems/textsecuregcm/entities/ClientContact.java @@ -35,13 +35,17 @@ public class ClientContact { @JsonProperty private boolean voice; + @JsonProperty + private boolean video; + private String relay; private boolean inactive; - public ClientContact(byte[] token, String relay, boolean voice) { + public ClientContact(byte[] token, String relay, boolean voice, boolean video) { this.token = token; this.relay = relay; this.voice = voice; + this.video = video; } public ClientContact() {} @@ -74,6 +78,14 @@ public class ClientContact { this.voice = voice; } + public boolean isVideo() { + return video; + } + + public void setVideo(boolean video) { + this.video = video; + } + @Override public boolean equals(Object other) { if (other == null) return false; @@ -85,6 +97,7 @@ public class ClientContact { Arrays.equals(this.token, that.token) && this.inactive == that.inactive && this.voice == that.voice && + this.video == that.video && (this.relay == null ? (that.relay == null) : this.relay.equals(that.relay)); } diff --git a/src/main/java/org/whispersystems/textsecuregcm/federation/NonLimitedAccount.java b/src/main/java/org/whispersystems/textsecuregcm/federation/NonLimitedAccount.java index 4c13c9f3d..509ac88f9 100644 --- a/src/main/java/org/whispersystems/textsecuregcm/federation/NonLimitedAccount.java +++ b/src/main/java/org/whispersystems/textsecuregcm/federation/NonLimitedAccount.java @@ -40,6 +40,6 @@ public class NonLimitedAccount extends Account { @Override public Optional getAuthenticatedDevice() { - return Optional.of(new Device(deviceId, null, null, null, null, null, null, null, false, 0, null, System.currentTimeMillis(), System.currentTimeMillis(), false, "NA")); + return Optional.of(new Device(deviceId, null, null, null, null, null, null, null, false, 0, null, System.currentTimeMillis(), System.currentTimeMillis(), false, false, "NA")); } } diff --git a/src/main/java/org/whispersystems/textsecuregcm/storage/Account.java b/src/main/java/org/whispersystems/textsecuregcm/storage/Account.java index 7c4367d2d..91656a08b 100644 --- a/src/main/java/org/whispersystems/textsecuregcm/storage/Account.java +++ b/src/main/java/org/whispersystems/textsecuregcm/storage/Account.java @@ -72,7 +72,7 @@ public class Account { } public void removeDevice(long deviceId) { - this.devices.remove(new Device(deviceId, null, null, null, null, null, null, null, false, 0, null, 0, 0, false, "NA")); + this.devices.remove(new Device(deviceId, null, null, null, null, null, null, null, false, 0, null, 0, 0, false, false, "NA")); } public Set getDevices() { @@ -103,6 +103,16 @@ public class Account { return false; } + public boolean isVideoSupported() { + for (Device device : devices) { + if (device.isActive() && device.isVideoSupported()) { + return true; + } + } + + return false; + } + public boolean isActive() { return getMasterDevice().isPresent() && diff --git a/src/main/java/org/whispersystems/textsecuregcm/storage/AccountsManager.java b/src/main/java/org/whispersystems/textsecuregcm/storage/AccountsManager.java index b8b526eaa..1de0acc85 100644 --- a/src/main/java/org/whispersystems/textsecuregcm/storage/AccountsManager.java +++ b/src/main/java/org/whispersystems/textsecuregcm/storage/AccountsManager.java @@ -102,7 +102,7 @@ public class AccountsManager { private void updateDirectory(Account account) { if (account.isActive()) { byte[] token = Util.getContactToken(account.getNumber()); - ClientContact clientContact = new ClientContact(token, null, account.isVoiceSupported()); + ClientContact clientContact = new ClientContact(token, null, account.isVoiceSupported(), account.isVideoSupported()); directory.add(clientContact); } else { directory.remove(account.getNumber()); diff --git a/src/main/java/org/whispersystems/textsecuregcm/storage/Device.java b/src/main/java/org/whispersystems/textsecuregcm/storage/Device.java index ad289417f..6e8402418 100644 --- a/src/main/java/org/whispersystems/textsecuregcm/storage/Device.java +++ b/src/main/java/org/whispersystems/textsecuregcm/storage/Device.java @@ -73,6 +73,9 @@ public class Device { @JsonProperty private boolean voice; + @JsonProperty + private boolean video; + @JsonProperty private String userAgent; @@ -82,7 +85,7 @@ public class Device { String signalingKey, String gcmId, String apnId, String voipApnId, boolean fetchesMessages, int registrationId, SignedPreKey signedPreKey, - long lastSeen, long created, boolean voice, + long lastSeen, long created, boolean voice, boolean video, String userAgent) { this.id = id; @@ -99,6 +102,7 @@ public class Device { this.lastSeen = lastSeen; this.created = created; this.voice = voice; + this.video = video; this.userAgent = userAgent; } @@ -174,6 +178,14 @@ public class Device { this.voice = voice; } + public boolean isVideoSupported() { + return video; + } + + public void setVideoSupported(boolean video) { + this.video = video; + } + public void setAuthenticationCredentials(AuthenticationCredentials credentials) { this.authToken = credentials.getHashedAuthenticationToken(); this.salt = credentials.getSalt(); diff --git a/src/main/java/org/whispersystems/textsecuregcm/storage/DirectoryManager.java b/src/main/java/org/whispersystems/textsecuregcm/storage/DirectoryManager.java index 97e46ea0a..47e077f8c 100644 --- a/src/main/java/org/whispersystems/textsecuregcm/storage/DirectoryManager.java +++ b/src/main/java/org/whispersystems/textsecuregcm/storage/DirectoryManager.java @@ -72,7 +72,7 @@ public class DirectoryManager { } public void add(ClientContact contact) { - TokenValue tokenValue = new TokenValue(contact.getRelay(), contact.isVoice()); + TokenValue tokenValue = new TokenValue(contact.getRelay(), contact.isVoice(), contact.isVideo()); try (Jedis jedis = redisPool.getResource()) { jedis.hset(DIRECTORY_KEY, contact.getToken(), objectMapper.writeValueAsBytes(tokenValue)); @@ -84,7 +84,7 @@ public class DirectoryManager { public void add(BatchOperationHandle handle, ClientContact contact) { try { Pipeline pipeline = handle.pipeline; - TokenValue tokenValue = new TokenValue(contact.getRelay(), contact.isVoice()); + TokenValue tokenValue = new TokenValue(contact.getRelay(), contact.isVoice(), contact.isVideo()); pipeline.hset(DIRECTORY_KEY, contact.getToken(), objectMapper.writeValueAsBytes(tokenValue)); } catch (JsonProcessingException e) { @@ -106,7 +106,7 @@ public class DirectoryManager { } TokenValue tokenValue = objectMapper.readValue(result, TokenValue.class); - return Optional.of(new ClientContact(token, tokenValue.relay, tokenValue.voice)); + return Optional.of(new ClientContact(token, tokenValue.relay, tokenValue.voice, tokenValue.video)); } catch (IOException e) { logger.warn("JSON Error", e); return Optional.absent(); @@ -133,7 +133,7 @@ public class DirectoryManager { try { if (pair.second().get() != null) { TokenValue tokenValue = objectMapper.readValue(pair.second().get(), TokenValue.class); - ClientContact clientContact = new ClientContact(pair.first(), tokenValue.relay, tokenValue.voice); + ClientContact clientContact = new ClientContact(pair.first(), tokenValue.relay, tokenValue.voice, tokenValue.video); results.add(clientContact); } @@ -178,11 +178,15 @@ public class DirectoryManager { @JsonProperty(value = "v") private boolean voice; + @JsonProperty(value = "w") + private boolean video; + public TokenValue() {} - public TokenValue(String relay, boolean voice) { + public TokenValue(String relay, boolean voice, boolean video) { this.relay = relay; this.voice = voice; + this.video = video; } } @@ -205,7 +209,7 @@ public class DirectoryManager { } TokenValue tokenValue = objectMapper.readValue(result, TokenValue.class); - return Optional.of(new ClientContact(token, tokenValue.relay, tokenValue.voice)); + return Optional.of(new ClientContact(token, tokenValue.relay, tokenValue.voice, tokenValue.video)); } } diff --git a/src/main/java/org/whispersystems/textsecuregcm/workers/DirectoryUpdater.java b/src/main/java/org/whispersystems/textsecuregcm/workers/DirectoryUpdater.java index 3a9d79bde..a6e36f6c4 100644 --- a/src/main/java/org/whispersystems/textsecuregcm/workers/DirectoryUpdater.java +++ b/src/main/java/org/whispersystems/textsecuregcm/workers/DirectoryUpdater.java @@ -68,7 +68,7 @@ public class DirectoryUpdater { for (Account account : accounts) { if (account.isActive()) { byte[] token = Util.getContactToken(account.getNumber()); - ClientContact clientContact = new ClientContact(token, null, account.isVoiceSupported()); + ClientContact clientContact = new ClientContact(token, null, account.isVoiceSupported(), account.isVideoSupported()); directory.add(batchOperation, clientContact); contactsAdded++; diff --git a/src/test/java/org/whispersystems/textsecuregcm/tests/controllers/DeviceControllerTest.java b/src/test/java/org/whispersystems/textsecuregcm/tests/controllers/DeviceControllerTest.java index 8422816ff..227aada12 100644 --- a/src/test/java/org/whispersystems/textsecuregcm/tests/controllers/DeviceControllerTest.java +++ b/src/test/java/org/whispersystems/textsecuregcm/tests/controllers/DeviceControllerTest.java @@ -142,7 +142,7 @@ public class DeviceControllerTest { .target("/v1/devices/5678901") .request() .header("Authorization", AuthHelper.getAuthHeader(AuthHelper.VALID_NUMBER, "password1")) - .put(Entity.entity(new AccountAttributes("keykeykeykey", false, 1234, "this is a really long name that is longer than 80 characters", true), + .put(Entity.entity(new AccountAttributes("keykeykeykey", false, 1234, "this is a really long name that is longer than 80 characters", true, true), MediaType.APPLICATION_JSON_TYPE)); assertEquals(response.getStatus(), 422); diff --git a/src/test/java/org/whispersystems/textsecuregcm/tests/controllers/FederatedControllerTest.java b/src/test/java/org/whispersystems/textsecuregcm/tests/controllers/FederatedControllerTest.java index 4b8a807bf..6d68770c5 100644 --- a/src/test/java/org/whispersystems/textsecuregcm/tests/controllers/FederatedControllerTest.java +++ b/src/test/java/org/whispersystems/textsecuregcm/tests/controllers/FederatedControllerTest.java @@ -79,12 +79,12 @@ public class FederatedControllerTest { @Before public void setup() throws Exception { Set singleDeviceList = new HashSet() {{ - add(new Device(1, null, "foo", "bar", "baz", "isgcm", null, null, false, 111, new SignedPreKey(111, "foo", "bar"), System.currentTimeMillis(), System.currentTimeMillis(), false, "Test")); + add(new Device(1, null, "foo", "bar", "baz", "isgcm", null, null, false, 111, new SignedPreKey(111, "foo", "bar"), System.currentTimeMillis(), System.currentTimeMillis(), false, false, "Test")); }}; Set multiDeviceList = new HashSet() {{ - add(new Device(1, null, "foo", "bar", "baz", "isgcm", null, null, false, 222, new SignedPreKey(222, "baz", "boop"), System.currentTimeMillis(), System.currentTimeMillis(), false, "Test")); - add(new Device(2, null, "foo", "bar", "baz", "isgcm", null, null, false, 333, new SignedPreKey(333, "rad", "mad"), System.currentTimeMillis(), System.currentTimeMillis(), false, "Test")); + add(new Device(1, null, "foo", "bar", "baz", "isgcm", null, null, false, 222, new SignedPreKey(222, "baz", "boop"), System.currentTimeMillis(), System.currentTimeMillis(), false, false, "Test")); + add(new Device(2, null, "foo", "bar", "baz", "isgcm", null, null, false, 333, new SignedPreKey(333, "rad", "mad"), System.currentTimeMillis(), System.currentTimeMillis(), false, false, "Test")); }}; Account singleDeviceAccount = new Account(SINGLE_DEVICE_RECIPIENT, singleDeviceList); diff --git a/src/test/java/org/whispersystems/textsecuregcm/tests/controllers/MessageControllerTest.java b/src/test/java/org/whispersystems/textsecuregcm/tests/controllers/MessageControllerTest.java index 48087ebe7..fff0dc1c7 100644 --- a/src/test/java/org/whispersystems/textsecuregcm/tests/controllers/MessageControllerTest.java +++ b/src/test/java/org/whispersystems/textsecuregcm/tests/controllers/MessageControllerTest.java @@ -74,13 +74,13 @@ public class MessageControllerTest { @Before public void setup() throws Exception { Set singleDeviceList = new HashSet() {{ - add(new Device(1, null, "foo", "bar", "baz", "isgcm", null, null, false, 111, new SignedPreKey(333, "baz", "boop"), System.currentTimeMillis(), System.currentTimeMillis(), false, "Test")); + add(new Device(1, null, "foo", "bar", "baz", "isgcm", null, null, false, 111, new SignedPreKey(333, "baz", "boop"), System.currentTimeMillis(), System.currentTimeMillis(), false, false, "Test")); }}; Set multiDeviceList = new HashSet() {{ - add(new Device(1, null, "foo", "bar", "baz", "isgcm", null, null, false, 222, new SignedPreKey(111, "foo", "bar"), System.currentTimeMillis(), System.currentTimeMillis(), false, "Test")); - add(new Device(2, null, "foo", "bar", "baz", "isgcm", null, null, false, 333, new SignedPreKey(222, "oof", "rab"), System.currentTimeMillis(), System.currentTimeMillis(), false, "Test")); - add(new Device(3, null, "foo", "bar", "baz", "isgcm", null, null, false, 444, null, System.currentTimeMillis() - TimeUnit.DAYS.toMillis(31), System.currentTimeMillis(), false, "Test")); + add(new Device(1, null, "foo", "bar", "baz", "isgcm", null, null, false, 222, new SignedPreKey(111, "foo", "bar"), System.currentTimeMillis(), System.currentTimeMillis(), false, false, "Test")); + add(new Device(2, null, "foo", "bar", "baz", "isgcm", null, null, false, 333, new SignedPreKey(222, "oof", "rab"), System.currentTimeMillis(), System.currentTimeMillis(), false, false, "Test")); + add(new Device(3, null, "foo", "bar", "baz", "isgcm", null, null, false, 444, null, System.currentTimeMillis() - TimeUnit.DAYS.toMillis(31), System.currentTimeMillis(), false, false, "Test")); }}; Account singleDeviceAccount = new Account(SINGLE_DEVICE_RECIPIENT, singleDeviceList); diff --git a/src/test/java/org/whispersystems/textsecuregcm/tests/controllers/ReceiptControllerTest.java b/src/test/java/org/whispersystems/textsecuregcm/tests/controllers/ReceiptControllerTest.java index 9f9989ec8..529009fbc 100644 --- a/src/test/java/org/whispersystems/textsecuregcm/tests/controllers/ReceiptControllerTest.java +++ b/src/test/java/org/whispersystems/textsecuregcm/tests/controllers/ReceiptControllerTest.java @@ -51,12 +51,12 @@ public class ReceiptControllerTest { @Before public void setup() throws Exception { Set singleDeviceList = new HashSet() {{ - add(new Device(1, null, "foo", "bar", "baz", "isgcm", null, null, false, 111, null, System.currentTimeMillis(), System.currentTimeMillis(), false, "Test")); + add(new Device(1, null, "foo", "bar", "baz", "isgcm", null, null, false, 111, null, System.currentTimeMillis(), System.currentTimeMillis(), false, false, "Test")); }}; Set multiDeviceList = new HashSet() {{ - add(new Device(1, null, "foo", "bar", "baz", "isgcm", null, null, false, 222, null, System.currentTimeMillis(), System.currentTimeMillis(), false, "Test")); - add(new Device(2, null, "foo", "bar", "baz", "isgcm", null, null, false, 333, null, System.currentTimeMillis(), System.currentTimeMillis(), false, "Test")); + add(new Device(1, null, "foo", "bar", "baz", "isgcm", null, null, false, 222, null, System.currentTimeMillis(), System.currentTimeMillis(), false, false, "Test")); + add(new Device(2, null, "foo", "bar", "baz", "isgcm", null, null, false, 333, null, System.currentTimeMillis(), System.currentTimeMillis(), false, false, "Test")); }}; Account singleDeviceAccount = new Account(SINGLE_DEVICE_RECIPIENT, singleDeviceList); diff --git a/src/test/java/org/whispersystems/textsecuregcm/tests/entities/ClientContactTest.java b/src/test/java/org/whispersystems/textsecuregcm/tests/entities/ClientContactTest.java index c23f459f9..d0cc19104 100644 --- a/src/test/java/org/whispersystems/textsecuregcm/tests/entities/ClientContactTest.java +++ b/src/test/java/org/whispersystems/textsecuregcm/tests/entities/ClientContactTest.java @@ -14,9 +14,10 @@ public class ClientContactTest { @Test public void serializeToJSON() throws Exception { byte[] token = Util.getContactToken("+14152222222"); - ClientContact contact = new ClientContact(token, null, false); - ClientContact contactWithRelay = new ClientContact(token, "whisper", false); - ClientContact contactWithRelayVox = new ClientContact(token, "whisper", true); + ClientContact contact = new ClientContact(token, null, false, false); + ClientContact contactWithRelay = new ClientContact(token, "whisper", false, false); + ClientContact contactWithRelayVox = new ClientContact(token, "whisper", true, false); + ClientContact contactWithRelayVid = new ClientContact(token, "whisper", true, true); assertThat("Basic Contact Serialization works", asJson(contact), @@ -29,12 +30,16 @@ public class ClientContactTest { assertThat("Contact Relay Vox Serializaton works", asJson(contactWithRelayVox), is(equalTo(jsonFixture("fixtures/contact.relay.voice.json")))); + + assertThat("Contact Relay Video Serializaton works", + asJson(contactWithRelayVid), + is(equalTo(jsonFixture("fixtures/contact.relay.video.json")))); } @Test public void deserializeFromJSON() throws Exception { ClientContact contact = new ClientContact(Util.getContactToken("+14152222222"), - "whisper", false); + "whisper", false, false); assertThat("a ClientContact can be deserialized from JSON", fromJson(jsonFixture("fixtures/contact.relay.json"), ClientContact.class), diff --git a/src/test/java/org/whispersystems/textsecuregcm/tests/entities/PreKeyTest.java b/src/test/java/org/whispersystems/textsecuregcm/tests/entities/PreKeyTest.java index fee62ab83..7b1056d20 100644 --- a/src/test/java/org/whispersystems/textsecuregcm/tests/entities/PreKeyTest.java +++ b/src/test/java/org/whispersystems/textsecuregcm/tests/entities/PreKeyTest.java @@ -26,7 +26,7 @@ public class PreKeyTest { @Test public void deserializeFromJSONV() throws Exception { ClientContact contact = new ClientContact(Util.getContactToken("+14152222222"), - "whisper", false); + "whisper", false, false); assertThat("a ClientContact can be deserialized from JSON", fromJson(jsonFixture("fixtures/contact.relay.json"), ClientContact.class), diff --git a/src/test/resources/fixtures/contact.relay.video.json b/src/test/resources/fixtures/contact.relay.video.json new file mode 100644 index 000000000..e242967ca --- /dev/null +++ b/src/test/resources/fixtures/contact.relay.video.json @@ -0,0 +1,6 @@ +{ + "token" : "BQVVHxMt5zAFXA", + "voice" : true, + "video" : true, + "relay" : "whisper" +} \ No newline at end of file