Smack/smack-omemo/src/main/java/org/jivesoftware/smackx/omemo/CachingOmemoStore.java

448 lines
16 KiB
Java

/**
*
* Copyright 2017 Paul Schaub
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jivesoftware.smackx.omemo;
import java.util.Date;
import java.util.HashMap;
import java.util.SortedSet;
import java.util.TreeMap;
import java.util.TreeSet;
import org.jivesoftware.smackx.omemo.exceptions.CorruptedOmemoKeyException;
import org.jivesoftware.smackx.omemo.internal.OmemoCachedDeviceList;
import org.jivesoftware.smackx.omemo.internal.OmemoDevice;
import org.jivesoftware.smackx.omemo.util.OmemoKeyUtil;
import org.jxmpp.jid.BareJid;
/**
* This class implements the Proxy Pattern in order to wrap an OmemoStore with a caching layer.
* This reduces access to the underlying storage layer (eg. database, filesystem) by only accessing it for
* missing/updated values.
*
* Alternatively this implementation can be used as an ephemeral keystore without a persisting backend.
*
* @param <T_IdKeyPair>
* @param <T_IdKey>
* @param <T_PreKey>
* @param <T_SigPreKey>
* @param <T_Sess>
* @param <T_Addr>
* @param <T_ECPub>
* @param <T_Bundle>
* @param <T_Ciph>
*/
public class CachingOmemoStore<T_IdKeyPair, T_IdKey, T_PreKey, T_SigPreKey, T_Sess, T_Addr, T_ECPub, T_Bundle, T_Ciph>
extends OmemoStore<T_IdKeyPair, T_IdKey, T_PreKey, T_SigPreKey, T_Sess, T_Addr, T_ECPub, T_Bundle, T_Ciph> {
private final HashMap<OmemoDevice, KeyCache<T_IdKeyPair, T_IdKey, T_PreKey, T_SigPreKey, T_Sess>> caches = new HashMap<>();
private final OmemoStore<T_IdKeyPair, T_IdKey, T_PreKey, T_SigPreKey, T_Sess, T_Addr, T_ECPub, T_Bundle, T_Ciph> persistent;
private final OmemoKeyUtil<T_IdKeyPair, T_IdKey, T_PreKey, T_SigPreKey, T_Sess, T_ECPub, T_Bundle> keyUtil;
public CachingOmemoStore(OmemoKeyUtil<T_IdKeyPair, T_IdKey, T_PreKey, T_SigPreKey, T_Sess, T_ECPub, T_Bundle> keyUtil) {
if (keyUtil == null) {
throw new IllegalArgumentException("KeyUtil MUST NOT be null!");
}
this.keyUtil = keyUtil;
persistent = null;
}
public CachingOmemoStore(OmemoStore<T_IdKeyPair, T_IdKey, T_PreKey, T_SigPreKey, T_Sess, T_Addr, T_ECPub, T_Bundle, T_Ciph> wrappedStore) {
if (wrappedStore == null) {
throw new NullPointerException("Wrapped OmemoStore MUST NOT be null!");
}
this.keyUtil = null;
persistent = wrappedStore;
}
@Override
public SortedSet<Integer> localDeviceIdsOf(BareJid localUser) {
if (persistent != null) {
return persistent.localDeviceIdsOf(localUser);
} else {
return new TreeSet<>(); //TODO: ?
}
}
@Override
public T_IdKeyPair loadOmemoIdentityKeyPair(OmemoDevice userDevice)
throws CorruptedOmemoKeyException {
T_IdKeyPair pair = getCache(userDevice).identityKeyPair;
if (pair == null && persistent != null) {
pair = persistent.loadOmemoIdentityKeyPair(userDevice);
if (pair != null) {
getCache(userDevice).identityKeyPair = pair;
}
}
return pair;
}
@Override
public void storeOmemoIdentityKeyPair(OmemoDevice userDevice, T_IdKeyPair identityKeyPair) {
getCache(userDevice).identityKeyPair = identityKeyPair;
if (persistent != null) {
persistent.storeOmemoIdentityKeyPair(userDevice, identityKeyPair);
}
}
@Override
public void removeOmemoIdentityKeyPair(OmemoDevice userDevice) {
getCache(userDevice).identityKeyPair = null;
if (persistent != null) {
persistent.removeOmemoIdentityKeyPair(userDevice);
}
}
@Override
public T_IdKey loadOmemoIdentityKey(OmemoDevice userDevice, OmemoDevice contactsDevice)
throws CorruptedOmemoKeyException {
T_IdKey idKey = getCache(userDevice).identityKeys.get(contactsDevice);
if (idKey == null && persistent != null) {
idKey = persistent.loadOmemoIdentityKey(userDevice, contactsDevice);
if (idKey != null) {
getCache(userDevice).identityKeys.put(contactsDevice, idKey);
}
}
return idKey;
}
@Override
public void storeOmemoIdentityKey(OmemoDevice userDevice, OmemoDevice device, T_IdKey t_idKey) {
getCache(userDevice).identityKeys.put(device, t_idKey);
if (persistent != null) {
persistent.storeOmemoIdentityKey(userDevice, device, t_idKey);
}
}
@Override
public void removeOmemoIdentityKey(OmemoDevice userDevice, OmemoDevice contactsDevice) {
getCache(userDevice).identityKeys.remove(contactsDevice);
if (persistent != null) {
persistent.removeOmemoIdentityKey(userDevice, contactsDevice);
}
}
@Override
public void setDateOfLastReceivedMessage(OmemoDevice userDevice, OmemoDevice from, Date date) {
getCache(userDevice).lastMessagesDates.put(from, date);
if (persistent != null) {
persistent.setDateOfLastReceivedMessage(userDevice, from, date);
}
}
@Override
public Date getDateOfLastReceivedMessage(OmemoDevice userDevice, OmemoDevice from) {
Date last = getCache(userDevice).lastMessagesDates.get(from);
if (last == null && persistent != null) {
last = persistent.getDateOfLastReceivedMessage(userDevice, from);
if (last != null) {
getCache(userDevice).lastMessagesDates.put(from, last);
}
}
return last;
}
@Override
public void setDateOfLastDeviceIdPublication(OmemoDevice userDevice, OmemoDevice contactsDevice, Date date) {
getCache(userDevice).lastDeviceIdPublicationDates.put(contactsDevice, date);
if (persistent != null) {
persistent.setDateOfLastReceivedMessage(userDevice, contactsDevice, date);
}
}
@Override
public Date getDateOfLastDeviceIdPublication(OmemoDevice userDevice, OmemoDevice contactsDevice) {
Date last = getCache(userDevice).lastDeviceIdPublicationDates.get(contactsDevice);
if (last == null && persistent != null) {
last = persistent.getDateOfLastDeviceIdPublication(userDevice, contactsDevice);
if (last != null) {
getCache(userDevice).lastDeviceIdPublicationDates.put(contactsDevice, last);
}
}
return last;
}
@Override
public void setDateOfLastSignedPreKeyRenewal(OmemoDevice userDevice, Date date) {
getCache(userDevice).lastRenewalDate = date;
if (persistent != null) {
persistent.setDateOfLastSignedPreKeyRenewal(userDevice, date);
}
}
@Override
public Date getDateOfLastSignedPreKeyRenewal(OmemoDevice userDevice) {
Date lastRenewal = getCache(userDevice).lastRenewalDate;
if (lastRenewal == null && persistent != null) {
lastRenewal = persistent.getDateOfLastSignedPreKeyRenewal(userDevice);
if (lastRenewal != null) {
getCache(userDevice).lastRenewalDate = lastRenewal;
}
}
return lastRenewal;
}
@Override
public T_PreKey loadOmemoPreKey(OmemoDevice userDevice, int preKeyId) {
T_PreKey preKey = getCache(userDevice).preKeys.get(preKeyId);
if (preKey == null && persistent != null) {
preKey = persistent.loadOmemoPreKey(userDevice, preKeyId);
if (preKey != null) {
getCache(userDevice).preKeys.put(preKeyId, preKey);
}
}
return preKey;
}
@Override
public void storeOmemoPreKey(OmemoDevice userDevice, int preKeyId, T_PreKey t_preKey) {
getCache(userDevice).preKeys.put(preKeyId, t_preKey);
if (persistent != null) {
persistent.storeOmemoPreKey(userDevice, preKeyId, t_preKey);
}
}
@Override
public void removeOmemoPreKey(OmemoDevice userDevice, int preKeyId) {
getCache(userDevice).preKeys.remove(preKeyId);
if (persistent != null) {
persistent.removeOmemoPreKey(userDevice, preKeyId);
}
}
@Override
public TreeMap<Integer, T_PreKey> loadOmemoPreKeys(OmemoDevice userDevice) {
TreeMap<Integer, T_PreKey> preKeys = getCache(userDevice).preKeys;
if (preKeys.isEmpty() && persistent != null) {
preKeys.putAll(persistent.loadOmemoPreKeys(userDevice));
}
return new TreeMap<>(preKeys);
}
@Override
public T_SigPreKey loadOmemoSignedPreKey(OmemoDevice userDevice, int signedPreKeyId) {
T_SigPreKey sigPreKey = getCache(userDevice).signedPreKeys.get(signedPreKeyId);
if (sigPreKey == null && persistent != null) {
sigPreKey = persistent.loadOmemoSignedPreKey(userDevice, signedPreKeyId);
if (sigPreKey != null) {
getCache(userDevice).signedPreKeys.put(signedPreKeyId, sigPreKey);
}
}
return sigPreKey;
}
@Override
public TreeMap<Integer, T_SigPreKey> loadOmemoSignedPreKeys(OmemoDevice userDevice) {
TreeMap<Integer, T_SigPreKey> sigPreKeys = getCache(userDevice).signedPreKeys;
if (sigPreKeys.isEmpty() && persistent != null) {
sigPreKeys.putAll(persistent.loadOmemoSignedPreKeys(userDevice));
}
return new TreeMap<>(sigPreKeys);
}
@Override
public void storeOmemoSignedPreKey(OmemoDevice userDevice,
int signedPreKeyId,
T_SigPreKey signedPreKey) {
getCache(userDevice).signedPreKeys.put(signedPreKeyId, signedPreKey);
if (persistent != null) {
persistent.storeOmemoSignedPreKey(userDevice, signedPreKeyId, signedPreKey);
}
}
@Override
public void removeOmemoSignedPreKey(OmemoDevice userDevice, int signedPreKeyId) {
getCache(userDevice).signedPreKeys.remove(signedPreKeyId);
if (persistent != null) {
persistent.removeOmemoSignedPreKey(userDevice, signedPreKeyId);
}
}
@Override
public T_Sess loadRawSession(OmemoDevice userDevice, OmemoDevice contactsDevice) {
HashMap<Integer, T_Sess> contactSessions = getCache(userDevice).sessions.get(contactsDevice.getJid());
if (contactSessions == null) {
contactSessions = new HashMap<>();
getCache(userDevice).sessions.put(contactsDevice.getJid(), contactSessions);
}
T_Sess session = contactSessions.get(contactsDevice.getDeviceId());
if (session == null && persistent != null) {
session = persistent.loadRawSession(userDevice, contactsDevice);
if (session != null) {
contactSessions.put(contactsDevice.getDeviceId(), session);
}
}
return session;
}
@Override
public HashMap<Integer, T_Sess> loadAllRawSessionsOf(OmemoDevice userDevice, BareJid contact) {
HashMap<Integer, T_Sess> sessions = getCache(userDevice).sessions.get(contact);
if (sessions == null) {
sessions = new HashMap<>();
getCache(userDevice).sessions.put(contact, sessions);
}
if (sessions.isEmpty() && persistent != null) {
sessions.putAll(persistent.loadAllRawSessionsOf(userDevice, contact));
}
return new HashMap<>(sessions);
}
@Override
public void storeRawSession(OmemoDevice userDevice, OmemoDevice contactsDevicece, T_Sess session) {
HashMap<Integer, T_Sess> sessions = getCache(userDevice).sessions.get(contactsDevicece.getJid());
if (sessions == null) {
sessions = new HashMap<>();
getCache(userDevice).sessions.put(contactsDevicece.getJid(), sessions);
}
sessions.put(contactsDevicece.getDeviceId(), session);
if (persistent != null) {
persistent.storeRawSession(userDevice, contactsDevicece, session);
}
}
@Override
public void removeRawSession(OmemoDevice userDevice, OmemoDevice contactsDevice) {
HashMap<Integer, T_Sess> sessions = getCache(userDevice).sessions.get(contactsDevice.getJid());
if (sessions != null) {
sessions.remove(contactsDevice.getDeviceId());
}
if (persistent != null) {
persistent.removeRawSession(userDevice, contactsDevice);
}
}
@Override
public void removeAllRawSessionsOf(OmemoDevice userDevice, BareJid contact) {
getCache(userDevice).sessions.remove(contact);
if (persistent != null) {
persistent.removeAllRawSessionsOf(userDevice, contact);
}
}
@Override
public boolean containsRawSession(OmemoDevice userDevice, OmemoDevice contactsDevice) {
HashMap<Integer, T_Sess> sessions = getCache(userDevice).sessions.get(contactsDevice.getJid());
return (sessions != null && sessions.get(contactsDevice.getDeviceId()) != null) ||
(persistent != null && persistent.containsRawSession(userDevice, contactsDevice));
}
@Override
public OmemoCachedDeviceList loadCachedDeviceList(OmemoDevice userDevice, BareJid contact) {
OmemoCachedDeviceList list = getCache(userDevice).deviceLists.get(contact);
if (list == null && persistent != null) {
list = persistent.loadCachedDeviceList(userDevice, contact);
if (list != null) {
getCache(userDevice).deviceLists.put(contact, list);
}
}
return list == null ? new OmemoCachedDeviceList() : new OmemoCachedDeviceList(list);
}
@Override
public void storeCachedDeviceList(OmemoDevice userDevice,
BareJid contact,
OmemoCachedDeviceList deviceList) {
getCache(userDevice).deviceLists.put(contact, new OmemoCachedDeviceList(deviceList));
if (persistent != null) {
persistent.storeCachedDeviceList(userDevice, contact, deviceList);
}
}
@Override
public void purgeOwnDeviceKeys(OmemoDevice userDevice) {
caches.remove(userDevice);
if (persistent != null) {
persistent.purgeOwnDeviceKeys(userDevice);
}
}
@Override
public OmemoKeyUtil<T_IdKeyPair, T_IdKey, T_PreKey, T_SigPreKey, T_Sess, T_ECPub, T_Bundle>
keyUtil() {
if (persistent != null) {
return persistent.keyUtil();
} else {
return keyUtil;
}
}
/**
* Return the {@link KeyCache} object of an {@link OmemoManager}.
* @param device
* @return
*/
private KeyCache<T_IdKeyPair, T_IdKey, T_PreKey, T_SigPreKey, T_Sess> getCache(OmemoDevice device) {
KeyCache<T_IdKeyPair, T_IdKey, T_PreKey, T_SigPreKey, T_Sess> cache = caches.get(device);
if (cache == null) {
cache = new KeyCache<>();
caches.put(device, cache);
}
return cache;
}
/**
* Cache that stores values for an {@link OmemoManager}.
* @param <T_IdKeyPair>
* @param <T_IdKey>
* @param <T_PreKey>
* @param <T_SigPreKey>
* @param <T_Sess>
*/
private static class KeyCache<T_IdKeyPair, T_IdKey, T_PreKey, T_SigPreKey, T_Sess> {
private T_IdKeyPair identityKeyPair;
private final TreeMap<Integer, T_PreKey> preKeys = new TreeMap<>();
private final TreeMap<Integer, T_SigPreKey> signedPreKeys = new TreeMap<>();
private final HashMap<BareJid, HashMap<Integer, T_Sess>> sessions = new HashMap<>();
private final HashMap<OmemoDevice, T_IdKey> identityKeys = new HashMap<>();
private final HashMap<OmemoDevice, Date> lastMessagesDates = new HashMap<>();
private final HashMap<OmemoDevice, Date> lastDeviceIdPublicationDates = new HashMap<>();
private final HashMap<BareJid, OmemoCachedDeviceList> deviceLists = new HashMap<>();
private Date lastRenewalDate = null;
private final HashMap<OmemoDevice, Integer> messageCounters = new HashMap<>();
}
}