Support for video account attributes

// FREEBIE
This commit is contained in:
Moxie Marlinspike 2017-01-09 11:50:56 -08:00
parent 5b28594189
commit 2dfe9eea94
17 changed files with 91 additions and 31 deletions

View File

@ -307,6 +307,7 @@ public class AccountController {
device.setName(attributes.getName()); device.setName(attributes.getName());
device.setLastSeen(Util.todayInMillis()); device.setLastSeen(Util.todayInMillis());
device.setVoiceSupported(attributes.getVoice()); device.setVoiceSupported(attributes.getVoice());
device.setVideoSupported(attributes.getVideo());
device.setRegistrationId(attributes.getRegistrationId()); device.setRegistrationId(attributes.getRegistrationId());
device.setSignalingKey(attributes.getSignalingKey()); device.setSignalingKey(attributes.getSignalingKey());
device.setUserAgent(userAgent); device.setUserAgent(userAgent);
@ -332,6 +333,7 @@ public class AccountController {
device.setRegistrationId(accountAttributes.getRegistrationId()); device.setRegistrationId(accountAttributes.getRegistrationId());
device.setName(accountAttributes.getName()); device.setName(accountAttributes.getName());
device.setVoiceSupported(accountAttributes.getVoice()); device.setVoiceSupported(accountAttributes.getVoice());
device.setVideoSupported(accountAttributes.getVideo());
device.setCreated(System.currentTimeMillis()); device.setCreated(System.currentTimeMillis());
device.setLastSeen(Util.todayInMillis()); device.setLastSeen(Util.todayInMillis());
device.setUserAgent(userAgent); device.setUserAgent(userAgent);

View File

@ -150,7 +150,7 @@ public class FederationControllerV1 extends FederationController {
for (Account account : accountList) { for (Account account : accountList) {
byte[] token = Util.getContactToken(account.getNumber()); 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()) { if (!account.isActive()) {
clientContact.setInactive(true); clientContact.setInactive(true);

View File

@ -40,20 +40,24 @@ public class AccountAttributes {
@JsonProperty @JsonProperty
private boolean voice; private boolean voice;
@JsonProperty
private boolean video;
public AccountAttributes() {} public AccountAttributes() {}
@VisibleForTesting @VisibleForTesting
public AccountAttributes(String signalingKey, boolean fetchesMessages, int registrationId) { public AccountAttributes(String signalingKey, boolean fetchesMessages, int registrationId) {
this(signalingKey, fetchesMessages, registrationId, null, false); this(signalingKey, fetchesMessages, registrationId, null, false, false);
} }
@VisibleForTesting @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.signalingKey = signalingKey;
this.fetchesMessages = fetchesMessages; this.fetchesMessages = fetchesMessages;
this.registrationId = registrationId; this.registrationId = registrationId;
this.name = name; this.name = name;
this.voice = voice; this.voice = voice;
this.video = video;
} }
public String getSignalingKey() { public String getSignalingKey() {
@ -76,4 +80,8 @@ public class AccountAttributes {
return voice; return voice;
} }
public boolean getVideo() {
return video;
}
} }

View File

@ -35,13 +35,17 @@ public class ClientContact {
@JsonProperty @JsonProperty
private boolean voice; private boolean voice;
@JsonProperty
private boolean video;
private String relay; private String relay;
private boolean inactive; 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.token = token;
this.relay = relay; this.relay = relay;
this.voice = voice; this.voice = voice;
this.video = video;
} }
public ClientContact() {} public ClientContact() {}
@ -74,6 +78,14 @@ public class ClientContact {
this.voice = voice; this.voice = voice;
} }
public boolean isVideo() {
return video;
}
public void setVideo(boolean video) {
this.video = video;
}
@Override @Override
public boolean equals(Object other) { public boolean equals(Object other) {
if (other == null) return false; if (other == null) return false;
@ -85,6 +97,7 @@ public class ClientContact {
Arrays.equals(this.token, that.token) && Arrays.equals(this.token, that.token) &&
this.inactive == that.inactive && this.inactive == that.inactive &&
this.voice == that.voice && this.voice == that.voice &&
this.video == that.video &&
(this.relay == null ? (that.relay == null) : this.relay.equals(that.relay)); (this.relay == null ? (that.relay == null) : this.relay.equals(that.relay));
} }

View File

@ -40,6 +40,6 @@ public class NonLimitedAccount extends Account {
@Override @Override
public Optional<Device> getAuthenticatedDevice() { public Optional<Device> 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"));
} }
} }

View File

@ -72,7 +72,7 @@ public class Account {
} }
public void removeDevice(long deviceId) { 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<Device> getDevices() { public Set<Device> getDevices() {
@ -103,6 +103,16 @@ public class Account {
return false; return false;
} }
public boolean isVideoSupported() {
for (Device device : devices) {
if (device.isActive() && device.isVideoSupported()) {
return true;
}
}
return false;
}
public boolean isActive() { public boolean isActive() {
return return
getMasterDevice().isPresent() && getMasterDevice().isPresent() &&

View File

@ -102,7 +102,7 @@ public class AccountsManager {
private void updateDirectory(Account account) { private void updateDirectory(Account account) {
if (account.isActive()) { if (account.isActive()) {
byte[] token = Util.getContactToken(account.getNumber()); 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); directory.add(clientContact);
} else { } else {
directory.remove(account.getNumber()); directory.remove(account.getNumber());

View File

@ -73,6 +73,9 @@ public class Device {
@JsonProperty @JsonProperty
private boolean voice; private boolean voice;
@JsonProperty
private boolean video;
@JsonProperty @JsonProperty
private String userAgent; private String userAgent;
@ -82,7 +85,7 @@ public class Device {
String signalingKey, String gcmId, String apnId, String signalingKey, String gcmId, String apnId,
String voipApnId, boolean fetchesMessages, String voipApnId, boolean fetchesMessages,
int registrationId, SignedPreKey signedPreKey, int registrationId, SignedPreKey signedPreKey,
long lastSeen, long created, boolean voice, long lastSeen, long created, boolean voice, boolean video,
String userAgent) String userAgent)
{ {
this.id = id; this.id = id;
@ -99,6 +102,7 @@ public class Device {
this.lastSeen = lastSeen; this.lastSeen = lastSeen;
this.created = created; this.created = created;
this.voice = voice; this.voice = voice;
this.video = video;
this.userAgent = userAgent; this.userAgent = userAgent;
} }
@ -174,6 +178,14 @@ public class Device {
this.voice = voice; this.voice = voice;
} }
public boolean isVideoSupported() {
return video;
}
public void setVideoSupported(boolean video) {
this.video = video;
}
public void setAuthenticationCredentials(AuthenticationCredentials credentials) { public void setAuthenticationCredentials(AuthenticationCredentials credentials) {
this.authToken = credentials.getHashedAuthenticationToken(); this.authToken = credentials.getHashedAuthenticationToken();
this.salt = credentials.getSalt(); this.salt = credentials.getSalt();

View File

@ -72,7 +72,7 @@ public class DirectoryManager {
} }
public void add(ClientContact contact) { 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()) { try (Jedis jedis = redisPool.getResource()) {
jedis.hset(DIRECTORY_KEY, contact.getToken(), objectMapper.writeValueAsBytes(tokenValue)); jedis.hset(DIRECTORY_KEY, contact.getToken(), objectMapper.writeValueAsBytes(tokenValue));
@ -84,7 +84,7 @@ public class DirectoryManager {
public void add(BatchOperationHandle handle, ClientContact contact) { public void add(BatchOperationHandle handle, ClientContact contact) {
try { try {
Pipeline pipeline = handle.pipeline; 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)); pipeline.hset(DIRECTORY_KEY, contact.getToken(), objectMapper.writeValueAsBytes(tokenValue));
} catch (JsonProcessingException e) { } catch (JsonProcessingException e) {
@ -106,7 +106,7 @@ public class DirectoryManager {
} }
TokenValue tokenValue = objectMapper.readValue(result, TokenValue.class); 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) { } catch (IOException e) {
logger.warn("JSON Error", e); logger.warn("JSON Error", e);
return Optional.absent(); return Optional.absent();
@ -133,7 +133,7 @@ public class DirectoryManager {
try { try {
if (pair.second().get() != null) { if (pair.second().get() != null) {
TokenValue tokenValue = objectMapper.readValue(pair.second().get(), TokenValue.class); 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); results.add(clientContact);
} }
@ -178,11 +178,15 @@ public class DirectoryManager {
@JsonProperty(value = "v") @JsonProperty(value = "v")
private boolean voice; private boolean voice;
@JsonProperty(value = "w")
private boolean video;
public TokenValue() {} public TokenValue() {}
public TokenValue(String relay, boolean voice) { public TokenValue(String relay, boolean voice, boolean video) {
this.relay = relay; this.relay = relay;
this.voice = voice; this.voice = voice;
this.video = video;
} }
} }
@ -205,7 +209,7 @@ public class DirectoryManager {
} }
TokenValue tokenValue = objectMapper.readValue(result, TokenValue.class); 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));
} }
} }

View File

@ -68,7 +68,7 @@ public class DirectoryUpdater {
for (Account account : accounts) { for (Account account : accounts) {
if (account.isActive()) { if (account.isActive()) {
byte[] token = Util.getContactToken(account.getNumber()); 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); directory.add(batchOperation, clientContact);
contactsAdded++; contactsAdded++;

View File

@ -142,7 +142,7 @@ public class DeviceControllerTest {
.target("/v1/devices/5678901") .target("/v1/devices/5678901")
.request() .request()
.header("Authorization", AuthHelper.getAuthHeader(AuthHelper.VALID_NUMBER, "password1")) .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)); MediaType.APPLICATION_JSON_TYPE));
assertEquals(response.getStatus(), 422); assertEquals(response.getStatus(), 422);

View File

@ -79,12 +79,12 @@ public class FederatedControllerTest {
@Before @Before
public void setup() throws Exception { public void setup() throws Exception {
Set<Device> singleDeviceList = new HashSet<Device>() {{ Set<Device> singleDeviceList = new HashSet<Device>() {{
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<Device> multiDeviceList = new HashSet<Device>() {{ Set<Device> multiDeviceList = new HashSet<Device>() {{
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(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, "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); Account singleDeviceAccount = new Account(SINGLE_DEVICE_RECIPIENT, singleDeviceList);

View File

@ -74,13 +74,13 @@ public class MessageControllerTest {
@Before @Before
public void setup() throws Exception { public void setup() throws Exception {
Set<Device> singleDeviceList = new HashSet<Device>() {{ Set<Device> singleDeviceList = new HashSet<Device>() {{
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<Device> multiDeviceList = new HashSet<Device>() {{ Set<Device> multiDeviceList = new HashSet<Device>() {{
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(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, "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, "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); Account singleDeviceAccount = new Account(SINGLE_DEVICE_RECIPIENT, singleDeviceList);

View File

@ -51,12 +51,12 @@ public class ReceiptControllerTest {
@Before @Before
public void setup() throws Exception { public void setup() throws Exception {
Set<Device> singleDeviceList = new HashSet<Device>() {{ Set<Device> singleDeviceList = new HashSet<Device>() {{
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<Device> multiDeviceList = new HashSet<Device>() {{ Set<Device> multiDeviceList = new HashSet<Device>() {{
add(new Device(1, null, "foo", "bar", "baz", "isgcm", null, null, false, 222, 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, "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); Account singleDeviceAccount = new Account(SINGLE_DEVICE_RECIPIENT, singleDeviceList);

View File

@ -14,9 +14,10 @@ public class ClientContactTest {
@Test @Test
public void serializeToJSON() throws Exception { public void serializeToJSON() throws Exception {
byte[] token = Util.getContactToken("+14152222222"); byte[] token = Util.getContactToken("+14152222222");
ClientContact contact = new ClientContact(token, null, false); ClientContact contact = new ClientContact(token, null, false, false);
ClientContact contactWithRelay = new ClientContact(token, "whisper", false); ClientContact contactWithRelay = new ClientContact(token, "whisper", false, false);
ClientContact contactWithRelayVox = new ClientContact(token, "whisper", true); ClientContact contactWithRelayVox = new ClientContact(token, "whisper", true, false);
ClientContact contactWithRelayVid = new ClientContact(token, "whisper", true, true);
assertThat("Basic Contact Serialization works", assertThat("Basic Contact Serialization works",
asJson(contact), asJson(contact),
@ -29,12 +30,16 @@ public class ClientContactTest {
assertThat("Contact Relay Vox Serializaton works", assertThat("Contact Relay Vox Serializaton works",
asJson(contactWithRelayVox), asJson(contactWithRelayVox),
is(equalTo(jsonFixture("fixtures/contact.relay.voice.json")))); 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 @Test
public void deserializeFromJSON() throws Exception { public void deserializeFromJSON() throws Exception {
ClientContact contact = new ClientContact(Util.getContactToken("+14152222222"), ClientContact contact = new ClientContact(Util.getContactToken("+14152222222"),
"whisper", false); "whisper", false, false);
assertThat("a ClientContact can be deserialized from JSON", assertThat("a ClientContact can be deserialized from JSON",
fromJson(jsonFixture("fixtures/contact.relay.json"), ClientContact.class), fromJson(jsonFixture("fixtures/contact.relay.json"), ClientContact.class),

View File

@ -26,7 +26,7 @@ public class PreKeyTest {
@Test @Test
public void deserializeFromJSONV() throws Exception { public void deserializeFromJSONV() throws Exception {
ClientContact contact = new ClientContact(Util.getContactToken("+14152222222"), ClientContact contact = new ClientContact(Util.getContactToken("+14152222222"),
"whisper", false); "whisper", false, false);
assertThat("a ClientContact can be deserialized from JSON", assertThat("a ClientContact can be deserialized from JSON",
fromJson(jsonFixture("fixtures/contact.relay.json"), ClientContact.class), fromJson(jsonFixture("fixtures/contact.relay.json"), ClientContact.class),

View File

@ -0,0 +1,6 @@
{
"token" : "BQVVHxMt5zAFXA",
"voice" : true,
"video" : true,
"relay" : "whisper"
}