/** * * 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 * @param * @param * @param * @param * @param * @param * @param * @param */ public class CachingOmemoStore extends OmemoStore { private final HashMap> caches = new HashMap<>(); private final OmemoStore persistent; private final OmemoKeyUtil keyUtil; public CachingOmemoStore(OmemoKeyUtil keyUtil) { if (keyUtil == null) { throw new IllegalArgumentException("KeyUtil MUST NOT be null!"); } this.keyUtil = keyUtil; persistent = null; } public CachingOmemoStore(OmemoStore wrappedStore) { if (wrappedStore == null) { throw new NullPointerException("Wrapped OmemoStore MUST NOT be null!"); } this.keyUtil = null; persistent = wrappedStore; } @Override public SortedSet 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 loadOmemoPreKeys(OmemoDevice userDevice) { TreeMap 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 loadOmemoSignedPreKeys(OmemoDevice userDevice) { TreeMap 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 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 loadAllRawSessionsOf(OmemoDevice userDevice, BareJid contact) { HashMap 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 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 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 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 keyUtil() { if (persistent != null) { return persistent.keyUtil(); } else { return keyUtil; } } /** * Return the {@link KeyCache} object of an {@link OmemoManager}. * @param device * @return */ private KeyCache getCache(OmemoDevice device) { KeyCache 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 * @param * @param * @param * @param */ private static class KeyCache { private T_IdKeyPair identityKeyPair; private final TreeMap preKeys = new TreeMap<>(); private final TreeMap signedPreKeys = new TreeMap<>(); private final HashMap> sessions = new HashMap<>(); private final HashMap identityKeys = new HashMap<>(); private final HashMap lastMessagesDates = new HashMap<>(); private final HashMap lastDeviceIdPublicationDates = new HashMap<>(); private final HashMap deviceLists = new HashMap<>(); private Date lastRenewalDate = null; private final HashMap messageCounters = new HashMap<>(); } }