Fixed merge issue with AxolotlService (differentiation between impl and interface)

This commit is contained in:
steckbrief 2016-03-08 12:55:03 +01:00
parent 2b86b686c5
commit 48f3922fe6
3 changed files with 171 additions and 133 deletions

View file

@ -9,6 +9,7 @@ import org.whispersystems.libaxolotl.state.PreKeyRecord;
import org.whispersystems.libaxolotl.state.SignedPreKeyRecord;
import java.security.cert.X509Certificate;
import java.util.List;
import java.util.Set;
import eu.siacs.conversations.entities.Account;
@ -30,6 +31,8 @@ public interface AxolotlService extends OnAdvancedStreamFeaturesLoaded {
String PEP_BUNDLES = PEP_PREFIX + ".bundles";
String PEP_VERIFICATION = PEP_PREFIX + ".verification";
int NUM_KEYS_TO_PUBLISH = 100;
enum FetchStatus {
PENDING,
SUCCESS,
@ -38,15 +41,10 @@ public interface AxolotlService extends OnAdvancedStreamFeaturesLoaded {
ERROR
}
boolean fetchMapHasErrors(Contact contact);
String getOwnFingerprint();
Set<IdentityKey> getKeysWithTrust(XmppAxolotlSession.Trust trust);
Set<IdentityKey> getKeysWithTrust(XmppAxolotlSession.Trust trust, Contact contact);
long getNumTrustedKeys(Contact contact);
Set<String> getFingerprintsForOwnSessions();
@ -77,8 +75,6 @@ public interface AxolotlService extends OnAdvancedStreamFeaturesLoaded {
void publishBundlesIfNeeded(boolean announce, boolean wipe);
boolean isContactAxolotlCapable(Contact contact);
XmppAxolotlSession.Trust getFingerprintTrust(String fingerprint);
X509Certificate getFingerprintCertificate(String fingerprint);
@ -87,24 +83,36 @@ public interface AxolotlService extends OnAdvancedStreamFeaturesLoaded {
Set<AxolotlAddress> findDevicesWithoutSession(Conversation conversation);
Set<AxolotlAddress> findDevicesWithoutSession(Jid contactJid);
boolean createSessionsIfNeeded(Conversation conversation);
boolean trustedSessionVerified(Conversation conversation);
boolean hasPendingKeyFetches(Account account, Contact contact);
@Nullable
XmppAxolotlMessage encrypt(Message message);
void preparePayloadMessage(Message message, boolean delay);
void prepareKeyTransportMessage(Contact contact, OnMessageCreatedCallback onMessageCreatedCallback);
XmppAxolotlMessage fetchAxolotlMessageFromCache(Message message);
XmppAxolotlMessage.XmppAxolotlPlaintextMessage processReceivingPayloadMessage(XmppAxolotlMessage message);
XmppAxolotlMessage.XmppAxolotlKeyTransportMessage processReceivingKeyTransportMessage(XmppAxolotlMessage message);
boolean fetchMapHasErrors(List<Jid> jids);
Set<IdentityKey> getKeysWithTrust(XmppAxolotlSession.Trust trust, Jid jid);
Set<IdentityKey> getKeysWithTrust(XmppAxolotlSession.Trust trust, List<Jid> jids);
long getNumTrustedKeys(Jid jid);
boolean anyTargetHasNoTrustedKeys(List<Jid> jids);
boolean isConversationAxolotlCapable(Conversation conversation);
List<Jid> getCryptoTargets(Conversation conversation);
boolean hasPendingKeyFetches(Account account, List<Jid> jids);
void prepareKeyTransportMessage(Conversation conversation, OnMessageCreatedCallback onMessageCreatedCallback);
}

View file

@ -51,7 +51,6 @@ import eu.siacs.conversations.xmpp.stanzas.IqPacket;
public class AxolotlServiceImpl implements AxolotlService {
public static final int NUM_KEYS_TO_PUBLISH = 100;
public static final int publishTriesThreshold = 3;
private final Account account;
@ -75,13 +74,14 @@ public class AxolotlServiceImpl implements AxolotlService {
}
@Override
public boolean fetchMapHasErrors(Contact contact) {
Jid jid = contact.getJid().toBareJid();
if (deviceIds.get(jid) != null) {
for (Integer foreignId : this.deviceIds.get(jid)) {
AxolotlAddress address = new AxolotlAddress(jid.toString(), foreignId);
if (fetchStatusMap.getAll(address).containsValue(FetchStatus.ERROR)) {
return true;
public boolean fetchMapHasErrors(List<Jid> jids) {
for(Jid jid : jids) {
if (deviceIds.get(jid) != null) {
for (Integer foreignId : this.deviceIds.get(jid)) {
AxolotlAddress address = new AxolotlAddress(jid.toString(), foreignId);
if (fetchStatusMap.getAll(address).containsValue(FetchStatus.ERROR)) {
return true;
}
}
}
}
@ -220,24 +220,41 @@ public class AxolotlServiceImpl implements AxolotlService {
this.executor = new SerialSingleThreadExecutor();
}
@Override
public String getOwnFingerprint() {
return axolotlStore.getIdentityKeyPair().getPublicKey().getFingerprint().replaceAll("\\s", "");
}
@Override
public Set<IdentityKey> getKeysWithTrust(XmppAxolotlSession.Trust trust) {
return axolotlStore.getContactKeysWithTrust(account.getJid().toBareJid().toString(), trust);
}
@Override
public Set<IdentityKey> getKeysWithTrust(XmppAxolotlSession.Trust trust, Contact contact) {
return axolotlStore.getContactKeysWithTrust(contact.getJid().toBareJid().toString(), trust);
public Set<IdentityKey> getKeysWithTrust(XmppAxolotlSession.Trust trust, Jid jid) {
return axolotlStore.getContactKeysWithTrust(jid.toBareJid().toString(), trust);
}
@Override
public long getNumTrustedKeys(Contact contact) {
return axolotlStore.getContactNumTrustedKeys(contact.getJid().toBareJid().toString());
public Set<IdentityKey> getKeysWithTrust(XmppAxolotlSession.Trust trust, List<Jid> jids) {
Set<IdentityKey> keys = new HashSet<>();
for(Jid jid : jids) {
keys.addAll(axolotlStore.getContactKeysWithTrust(jid.toString(), trust));
}
return keys;
}
@Override
public long getNumTrustedKeys(Jid jid) {
return axolotlStore.getContactNumTrustedKeys(jid.toBareJid().toString());
}
@Override
public boolean anyTargetHasNoTrustedKeys(List<Jid> jids) {
for(Jid jid : jids) {
if (axolotlStore.getContactNumTrustedKeys(jid.toBareJid().toString()) == 0) {
return true;
}
}
return false;
}
private AxolotlAddress getAddressForJid(Jid jid) {
@ -249,12 +266,19 @@ public class AxolotlServiceImpl implements AxolotlService {
return new HashSet<>(this.sessions.getAll(ownAddress).values());
}
private Set<XmppAxolotlSession> findSessionsforContact(Contact contact) {
private Set<XmppAxolotlSession> findSessionsForContact(Contact contact) {
AxolotlAddress contactAddress = getAddressForJid(contact.getJid());
return new HashSet<>(this.sessions.getAll(contactAddress).values());
}
@Override
private Set<XmppAxolotlSession> findSessionsForConversation(Conversation conversation) {
HashSet<XmppAxolotlSession> sessions = new HashSet<>();
for(Jid jid : conversation.getAcceptedCryptoTargets()) {
sessions.addAll(this.sessions.getAll(getAddressForJid(jid)).values());
}
return sessions;
}
public Set<String> getFingerprintsForOwnSessions() {
Set<String> fingerprints = new HashSet<>();
for (XmppAxolotlSession session : findOwnSessions()) {
@ -263,26 +287,22 @@ public class AxolotlServiceImpl implements AxolotlService {
return fingerprints;
}
@Override
public Set<String> getFingerprintsForContact(final Contact contact) {
Set<String> fingerprints = new HashSet<>();
for (XmppAxolotlSession session : findSessionsforContact(contact)) {
for (XmppAxolotlSession session : findSessionsForContact(contact)) {
fingerprints.add(session.getFingerprint());
}
return fingerprints;
}
private boolean hasAny(Contact contact) {
AxolotlAddress contactAddress = getAddressForJid(contact.getJid());
return sessions.hasAny(contactAddress);
private boolean hasAny(Jid jid) {
return sessions.hasAny(getAddressForJid(jid));
}
@Override
public boolean isPepBroken() {
return this.pepBroken;
}
@Override
public void regenerateKeys(boolean wipeOther) {
axolotlStore.regenerate();
sessions.clear();
@ -290,19 +310,17 @@ public class AxolotlServiceImpl implements AxolotlService {
publishBundlesIfNeeded(true, wipeOther);
}
@Override
public int getOwnDeviceId() {
return axolotlStore.getLocalRegistrationId();
}
@Override
public Set<Integer> getOwnDeviceIds() {
return this.deviceIds.get(account.getJid().toBareJid());
}
private void setTrustOnSessions(final Jid jid, @NonNull final Set<Integer> deviceIds,
final XmppAxolotlSession.Trust from,
final XmppAxolotlSession.Trust to) {
final XmppAxolotlSession.Trust from,
final XmppAxolotlSession.Trust to) {
for (Integer deviceId : deviceIds) {
AxolotlAddress address = new AxolotlAddress(jid.toBareJid().toString(), deviceId);
XmppAxolotlSession session = sessions.get(address);
@ -313,7 +331,6 @@ public class AxolotlServiceImpl implements AxolotlService {
}
}
@Override
public void registerDevices(final Jid jid, @NonNull final Set<Integer> deviceIds) {
if (jid.toBareJid().equals(account.getJid().toBareJid())) {
if (!deviceIds.isEmpty()) {
@ -356,7 +373,6 @@ public class AxolotlServiceImpl implements AxolotlService {
mXmppConnectionService.keyStatusUpdated(null);
}
@Override
public void wipeOtherPepDevices() {
if (pepBroken) {
Log.d(Config.LOGTAG, getLogprefix(account) + "wipeOtherPepDevices called, but PEP is broken. Ignoring... ");
@ -374,12 +390,10 @@ public class AxolotlServiceImpl implements AxolotlService {
});
}
@Override
public void purgeKey(final String fingerprint) {
axolotlStore.setFingerprintTrust(fingerprint.replaceAll("\\s", ""), XmppAxolotlSession.Trust.COMPROMISED);
}
@Override
public void publishOwnDeviceIdIfNeeded() {
if (pepBroken) {
Log.d(Config.LOGTAG, getLogprefix(account) + "publishOwnDeviceIdIfNeeded called, but PEP is broken. Ignoring... ");
@ -402,7 +416,6 @@ public class AxolotlServiceImpl implements AxolotlService {
});
}
@Override
public void publishOwnDeviceId(Set<Integer> deviceIds) {
Set<Integer> deviceIdsCopy = new HashSet<>(deviceIds);
if (!deviceIdsCopy.contains(getOwnDeviceId())) {
@ -432,7 +445,6 @@ public class AxolotlServiceImpl implements AxolotlService {
}
}
@Override
public void publishDeviceVerificationAndBundle(final SignedPreKeyRecord signedPreKeyRecord,
final Set<PreKeyRecord> preKeyRecords,
final boolean announceAfter,
@ -458,7 +470,6 @@ public class AxolotlServiceImpl implements AxolotlService {
}
}
@Override
public void publishBundlesIfNeeded(final boolean announce, final boolean wipe) {
if (pepBroken) {
Log.d(Config.LOGTAG, getLogprefix(account) + "publishBundlesIfNeeded called, but PEP is broken. Ignoring... ");
@ -598,23 +609,36 @@ public class AxolotlServiceImpl implements AxolotlService {
}
@Override
public boolean isContactAxolotlCapable(Contact contact) {
Jid jid = contact.getJid().toBareJid();
return hasAny(contact) ||
(deviceIds.containsKey(jid) && !deviceIds.get(jid).isEmpty());
public boolean isConversationAxolotlCapable(Conversation conversation) {
final List<Jid> jids = getCryptoTargets(conversation);
for(Jid jid : jids) {
if (!hasAny(jid) && (!deviceIds.containsKey(jid) || deviceIds.get(jid).isEmpty())) {
return false;
}
}
return jids.size() > 0;
}
@Override
public List<Jid> getCryptoTargets(Conversation conversation) {
final List<Jid> jids;
if (conversation.getMode() == Conversation.MODE_SINGLE) {
jids = Arrays.asList(conversation.getJid().toBareJid());
} else {
jids = conversation.getMucOptions().getMembers();
jids.remove(account.getJid().toBareJid());
}
return jids;
}
public XmppAxolotlSession.Trust getFingerprintTrust(String fingerprint) {
return axolotlStore.getFingerprintTrust(fingerprint);
}
@Override
public X509Certificate getFingerprintCertificate(String fingerprint) {
return axolotlStore.getFingerprintCertificate(fingerprint);
}
@Override
public void setFingerprintTrust(String fingerprint, XmppAxolotlSession.Trust trust) {
axolotlStore.setFingerprintTrust(fingerprint, trust);
}
@ -759,36 +783,32 @@ public class AxolotlServiceImpl implements AxolotlService {
}
}
@Override
public Set<AxolotlAddress> findDevicesWithoutSession(final Conversation conversation) {
return findDevicesWithoutSession(conversation.getContact().getJid().toBareJid());
}
@Override
public Set<AxolotlAddress> findDevicesWithoutSession(final Jid contactJid) {
Log.d(Config.LOGTAG, AxolotlServiceImpl.getLogprefix(account) + "Finding devices without session for " + contactJid);
Set<AxolotlAddress> addresses = new HashSet<>();
if (deviceIds.get(contactJid) != null) {
for (Integer foreignId : this.deviceIds.get(contactJid)) {
AxolotlAddress address = new AxolotlAddress(contactJid.toString(), foreignId);
if (sessions.get(address) == null) {
IdentityKey identityKey = axolotlStore.loadSession(address).getSessionState().getRemoteIdentityKey();
if (identityKey != null) {
Log.d(Config.LOGTAG, AxolotlServiceImpl.getLogprefix(account) + "Already have session for " + address.toString() + ", adding to cache...");
XmppAxolotlSession session = new XmppAxolotlSession(account, axolotlStore, address, identityKey);
sessions.put(address, session);
} else {
Log.d(Config.LOGTAG, AxolotlServiceImpl.getLogprefix(account) + "Found device " + contactJid + ":" + foreignId);
if (fetchStatusMap.get(address) != FetchStatus.ERROR) {
addresses.add(address);
for(Jid jid : getCryptoTargets(conversation)) {
Log.d(Config.LOGTAG, AxolotlServiceImpl.getLogprefix(account) + "Finding devices without session for " + jid);
if (deviceIds.get(jid) != null) {
for (Integer foreignId : this.deviceIds.get(jid)) {
AxolotlAddress address = new AxolotlAddress(jid.toString(), foreignId);
if (sessions.get(address) == null) {
IdentityKey identityKey = axolotlStore.loadSession(address).getSessionState().getRemoteIdentityKey();
if (identityKey != null) {
Log.d(Config.LOGTAG, AxolotlServiceImpl.getLogprefix(account) + "Already have session for " + address.toString() + ", adding to cache...");
XmppAxolotlSession session = new XmppAxolotlSession(account, axolotlStore, address, identityKey);
sessions.put(address, session);
} else {
Log.d(Config.LOGTAG,getLogprefix(account)+"skipping over "+address+" because it's broken");
Log.d(Config.LOGTAG, AxolotlServiceImpl.getLogprefix(account) + "Found device " + jid + ":" + foreignId);
if (fetchStatusMap.get(address) != FetchStatus.ERROR) {
addresses.add(address);
} else {
Log.d(Config.LOGTAG, getLogprefix(account) + "skipping over " + address + " because it's broken");
}
}
}
}
} else {
Log.w(Config.LOGTAG, AxolotlServiceImpl.getLogprefix(account) + "Have no target devices in PEP!");
}
} else {
Log.w(Config.LOGTAG, AxolotlServiceImpl.getLogprefix(account) + "Have no target devices in PEP!");
}
if (deviceIds.get(account.getJid().toBareJid()) != null) {
for (Integer ownId : this.deviceIds.get(account.getJid().toBareJid())) {
@ -814,7 +834,6 @@ public class AxolotlServiceImpl implements AxolotlService {
return addresses;
}
@Override
public boolean createSessionsIfNeeded(final Conversation conversation) {
Log.i(Config.LOGTAG, AxolotlServiceImpl.getLogprefix(account) + "Creating axolotl sessions if needed...");
boolean newSessions = false;
@ -836,9 +855,8 @@ public class AxolotlServiceImpl implements AxolotlService {
return newSessions;
}
@Override
public boolean trustedSessionVerified(final Conversation conversation) {
Set<XmppAxolotlSession> sessions = findSessionsforContact(conversation.getContact());
Set<XmppAxolotlSession> sessions = findSessionsForConversation(conversation);
sessions.addAll(findOwnSessions());
boolean verified = false;
for(XmppAxolotlSession session : sessions) {
@ -854,26 +872,32 @@ public class AxolotlServiceImpl implements AxolotlService {
}
@Override
public boolean hasPendingKeyFetches(Account account, Contact contact) {
public boolean hasPendingKeyFetches(Account account, List<Jid> jids) {
AxolotlAddress ownAddress = new AxolotlAddress(account.getJid().toBareJid().toString(), 0);
AxolotlAddress foreignAddress = new AxolotlAddress(contact.getJid().toBareJid().toString(), 0);
return fetchStatusMap.getAll(ownAddress).containsValue(FetchStatus.PENDING)
|| fetchStatusMap.getAll(foreignAddress).containsValue(FetchStatus.PENDING);
if (fetchStatusMap.getAll(ownAddress).containsValue(FetchStatus.PENDING)) {
return true;
}
for(Jid jid : jids) {
AxolotlAddress foreignAddress = new AxolotlAddress(jid.toBareJid().toString(), 0);
if (fetchStatusMap.getAll(foreignAddress).containsValue(FetchStatus.PENDING)) {
return true;
}
}
return false;
}
@Nullable
private XmppAxolotlMessage buildHeader(Contact contact) {
private XmppAxolotlMessage buildHeader(Conversation conversation) {
final XmppAxolotlMessage axolotlMessage = new XmppAxolotlMessage(
contact.getJid().toBareJid(), getOwnDeviceId());
account.getJid().toBareJid(), getOwnDeviceId());
Set<XmppAxolotlSession> contactSessions = findSessionsforContact(contact);
Set<XmppAxolotlSession> remoteSessions = findSessionsForConversation(conversation);
Set<XmppAxolotlSession> ownSessions = findOwnSessions();
if (contactSessions.isEmpty()) {
if (remoteSessions.isEmpty()) {
return null;
}
Log.d(Config.LOGTAG, AxolotlServiceImpl.getLogprefix(account) + "Building axolotl foreign keyElements...");
for (XmppAxolotlSession session : contactSessions) {
for (XmppAxolotlSession session : remoteSessions) {
Log.v(Config.LOGTAG, AxolotlServiceImpl.getLogprefix(account) + session.getRemoteAddress().toString());
axolotlMessage.addDevice(session);
}
@ -886,10 +910,9 @@ public class AxolotlServiceImpl implements AxolotlService {
return axolotlMessage;
}
@Override
@Nullable
public XmppAxolotlMessage encrypt(Message message) {
XmppAxolotlMessage axolotlMessage = buildHeader(message.getContact());
XmppAxolotlMessage axolotlMessage = buildHeader(message.getConversation());
if (axolotlMessage != null) {
final String content;
@ -909,7 +932,6 @@ public class AxolotlServiceImpl implements AxolotlService {
return axolotlMessage;
}
@Override
public void preparePayloadMessage(final Message message, final boolean delay) {
executor.execute(new Runnable() {
@Override
@ -928,17 +950,16 @@ public class AxolotlServiceImpl implements AxolotlService {
}
@Override
public void prepareKeyTransportMessage(final Contact contact, final OnMessageCreatedCallback onMessageCreatedCallback) {
public void prepareKeyTransportMessage(final Conversation conversation, final OnMessageCreatedCallback onMessageCreatedCallback) {
executor.execute(new Runnable() {
@Override
public void run() {
XmppAxolotlMessage axolotlMessage = buildHeader(contact);
XmppAxolotlMessage axolotlMessage = buildHeader(conversation);
onMessageCreatedCallback.run(axolotlMessage);
}
});
}
@Override
public XmppAxolotlMessage fetchAxolotlMessageFromCache(Message message) {
XmppAxolotlMessage axolotlMessage = messageCache.get(message.getUuid());
if (axolotlMessage != null) {
@ -971,7 +992,6 @@ public class AxolotlServiceImpl implements AxolotlService {
return session;
}
@Override
public XmppAxolotlMessage.XmppAxolotlPlaintextMessage processReceivingPayloadMessage(XmppAxolotlMessage message) {
XmppAxolotlMessage.XmppAxolotlPlaintextMessage plaintextMessage = null;
@ -994,7 +1014,6 @@ public class AxolotlServiceImpl implements AxolotlService {
return plaintextMessage;
}
@Override
public XmppAxolotlMessage.XmppAxolotlKeyTransportMessage processReceivingKeyTransportMessage(XmppAxolotlMessage message) {
XmppAxolotlMessage.XmppAxolotlKeyTransportMessage keyTransportMessage;
@ -1019,4 +1038,4 @@ public class AxolotlServiceImpl implements AxolotlService {
}
}
}
}
}

View file

@ -10,6 +10,7 @@ import org.whispersystems.libaxolotl.state.SignedPreKeyRecord;
import java.security.cert.X509Certificate;
import java.util.Collections;
import java.util.List;
import java.util.Set;
import eu.siacs.conversations.entities.Account;
@ -23,11 +24,6 @@ import eu.siacs.conversations.xmpp.jid.Jid;
*/
public class AxolotlServiceStub implements AxolotlService {
@Override
public boolean fetchMapHasErrors(Contact contact) {
return false;
}
@Override
public String getOwnFingerprint() {
return null;
@ -38,16 +34,6 @@ public class AxolotlServiceStub implements AxolotlService {
return Collections.emptySet();
}
@Override
public Set<IdentityKey> getKeysWithTrust(XmppAxolotlSession.Trust trust, Contact contact) {
return Collections.emptySet();
}
@Override
public long getNumTrustedKeys(Contact contact) {
return 0;
}
@Override
public Set<String> getFingerprintsForOwnSessions() {
return Collections.emptySet();
@ -113,11 +99,6 @@ public class AxolotlServiceStub implements AxolotlService {
}
@Override
public boolean isContactAxolotlCapable(Contact contact) {
return false;
}
@Override
public XmppAxolotlSession.Trust getFingerprintTrust(String fingerprint) {
return XmppAxolotlSession.Trust.TRUSTED;
@ -138,11 +119,6 @@ public class AxolotlServiceStub implements AxolotlService {
return Collections.emptySet();
}
@Override
public Set<AxolotlAddress> findDevicesWithoutSession(Jid contactJid) {
return Collections.emptySet();
}
@Override
public boolean createSessionsIfNeeded(Conversation conversation) {
return false;
@ -153,11 +129,6 @@ public class AxolotlServiceStub implements AxolotlService {
return false;
}
@Override
public boolean hasPendingKeyFetches(Account account, Contact contact) {
return false;
}
@Nullable
@Override
public XmppAxolotlMessage encrypt(Message message) {
@ -169,11 +140,6 @@ public class AxolotlServiceStub implements AxolotlService {
}
@Override
public void prepareKeyTransportMessage(Contact contact, OnMessageCreatedCallback onMessageCreatedCallback) {
}
@Override
public XmppAxolotlMessage fetchAxolotlMessageFromCache(Message message) {
return null;
@ -189,6 +155,51 @@ public class AxolotlServiceStub implements AxolotlService {
return null;
}
@Override
public boolean fetchMapHasErrors(List<Jid> jids) {
return false;
}
@Override
public Set<IdentityKey> getKeysWithTrust(XmppAxolotlSession.Trust trust, Jid jid) {
return Collections.emptySet();
}
@Override
public Set<IdentityKey> getKeysWithTrust(XmppAxolotlSession.Trust trust, List<Jid> jids) {
return Collections.emptySet();
}
@Override
public long getNumTrustedKeys(Jid jid) {
return 0;
}
@Override
public boolean anyTargetHasNoTrustedKeys(List<Jid> jids) {
return false;
}
@Override
public boolean isConversationAxolotlCapable(Conversation conversation) {
return false;
}
@Override
public List<Jid> getCryptoTargets(Conversation conversation) {
return Collections.emptyList();
}
@Override
public boolean hasPendingKeyFetches(Account account, List<Jid> jids) {
return false;
}
@Override
public void prepareKeyTransportMessage(Conversation conversation, OnMessageCreatedCallback onMessageCreatedCallback) {
}
@Override
public void onAdvancedStreamFeaturesAvailable(Account account) {