001/**
002 *
003 * Copyright 2017 Paul Schaub
004 *
005 * Licensed under the Apache License, Version 2.0 (the "License");
006 * you may not use this file except in compliance with the License.
007 * You may obtain a copy of the License at
008 *
009 *     http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 */
017package org.jivesoftware.smackx.omemo;
018
019import java.util.Date;
020import java.util.HashMap;
021import java.util.SortedSet;
022import java.util.TreeMap;
023import java.util.TreeSet;
024
025import org.jivesoftware.smackx.omemo.exceptions.CorruptedOmemoKeyException;
026import org.jivesoftware.smackx.omemo.internal.OmemoCachedDeviceList;
027import org.jivesoftware.smackx.omemo.internal.OmemoDevice;
028import org.jivesoftware.smackx.omemo.util.OmemoKeyUtil;
029
030import org.jxmpp.jid.BareJid;
031
032/**
033 * This class implements the Proxy Pattern in order to wrap an OmemoStore with a caching layer.
034 * This reduces access to the underlying storage layer (eg. database, filesystem) by only accessing it for
035 * missing/updated values.
036 *
037 * Alternatively this implementation can be used as an ephemeral keystore without a persisting backend.
038 *
039 * @param <T_IdKeyPair>
040 * @param <T_IdKey>
041 * @param <T_PreKey>
042 * @param <T_SigPreKey>
043 * @param <T_Sess>
044 * @param <T_Addr>
045 * @param <T_ECPub>
046 * @param <T_Bundle>
047 * @param <T_Ciph>
048 */
049public class CachingOmemoStore<T_IdKeyPair, T_IdKey, T_PreKey, T_SigPreKey, T_Sess, T_Addr, T_ECPub, T_Bundle, T_Ciph>
050        extends OmemoStore<T_IdKeyPair, T_IdKey, T_PreKey, T_SigPreKey, T_Sess, T_Addr, T_ECPub, T_Bundle, T_Ciph> {
051
052    private final HashMap<OmemoDevice, KeyCache<T_IdKeyPair, T_IdKey, T_PreKey, T_SigPreKey, T_Sess>> caches = new HashMap<>();
053    private final OmemoStore<T_IdKeyPair, T_IdKey, T_PreKey, T_SigPreKey, T_Sess, T_Addr, T_ECPub, T_Bundle, T_Ciph> persistent;
054    private final OmemoKeyUtil<T_IdKeyPair, T_IdKey, T_PreKey, T_SigPreKey, T_Sess, T_ECPub, T_Bundle> keyUtil;
055
056    public CachingOmemoStore(OmemoKeyUtil<T_IdKeyPair, T_IdKey, T_PreKey, T_SigPreKey, T_Sess, T_ECPub, T_Bundle> keyUtil) {
057        if (keyUtil == null) {
058            throw new IllegalArgumentException("KeyUtil MUST NOT be null!");
059        }
060        this.keyUtil = keyUtil;
061        persistent = null;
062    }
063
064    public CachingOmemoStore(OmemoStore<T_IdKeyPair, T_IdKey, T_PreKey, T_SigPreKey, T_Sess, T_Addr, T_ECPub, T_Bundle, T_Ciph> wrappedStore) {
065        if (wrappedStore == null) {
066            throw new NullPointerException("Wrapped OmemoStore MUST NOT be null!");
067        }
068        this.keyUtil = null;
069        persistent = wrappedStore;
070    }
071
072    @Override
073    public SortedSet<Integer> localDeviceIdsOf(BareJid localUser) {
074        if (persistent != null) {
075            return persistent.localDeviceIdsOf(localUser);
076        } else {
077            return new TreeSet<>(); //TODO: ?
078        }
079    }
080
081    @Override
082    public T_IdKeyPair loadOmemoIdentityKeyPair(OmemoDevice userDevice)
083            throws CorruptedOmemoKeyException {
084        T_IdKeyPair pair = getCache(userDevice).identityKeyPair;
085
086        if (pair == null && persistent != null) {
087            pair = persistent.loadOmemoIdentityKeyPair(userDevice);
088            if (pair != null) {
089                getCache(userDevice).identityKeyPair = pair;
090            }
091        }
092
093        return pair;
094    }
095
096    @Override
097    public void storeOmemoIdentityKeyPair(OmemoDevice userDevice, T_IdKeyPair identityKeyPair) {
098        getCache(userDevice).identityKeyPair = identityKeyPair;
099        if (persistent != null) {
100            persistent.storeOmemoIdentityKeyPair(userDevice, identityKeyPair);
101        }
102    }
103
104    @Override
105    public void removeOmemoIdentityKeyPair(OmemoDevice userDevice) {
106        getCache(userDevice).identityKeyPair = null;
107        if (persistent != null) {
108            persistent.removeOmemoIdentityKeyPair(userDevice);
109        }
110    }
111
112    @Override
113    public T_IdKey loadOmemoIdentityKey(OmemoDevice userDevice, OmemoDevice contactsDevice)
114            throws CorruptedOmemoKeyException {
115        T_IdKey idKey = getCache(userDevice).identityKeys.get(contactsDevice);
116
117        if (idKey == null && persistent != null) {
118            idKey = persistent.loadOmemoIdentityKey(userDevice, contactsDevice);
119            if (idKey != null) {
120                getCache(userDevice).identityKeys.put(contactsDevice, idKey);
121            }
122        }
123
124        return idKey;
125    }
126
127    @Override
128    public void storeOmemoIdentityKey(OmemoDevice userDevice, OmemoDevice device, T_IdKey t_idKey) {
129        getCache(userDevice).identityKeys.put(device, t_idKey);
130        if (persistent != null) {
131            persistent.storeOmemoIdentityKey(userDevice, device, t_idKey);
132        }
133    }
134
135    @Override
136    public void removeOmemoIdentityKey(OmemoDevice userDevice, OmemoDevice contactsDevice) {
137        getCache(userDevice).identityKeys.remove(contactsDevice);
138        if (persistent != null) {
139            persistent.removeOmemoIdentityKey(userDevice, contactsDevice);
140        }
141    }
142
143    @Override
144    public void setDateOfLastReceivedMessage(OmemoDevice userDevice, OmemoDevice from, Date date) {
145        getCache(userDevice).lastMessagesDates.put(from, date);
146        if (persistent != null) {
147            persistent.setDateOfLastReceivedMessage(userDevice, from, date);
148        }
149    }
150
151    @Override
152    public Date getDateOfLastReceivedMessage(OmemoDevice userDevice, OmemoDevice from) {
153        Date last = getCache(userDevice).lastMessagesDates.get(from);
154
155        if (last == null && persistent != null) {
156            last = persistent.getDateOfLastReceivedMessage(userDevice, from);
157            if (last != null) {
158                getCache(userDevice).lastMessagesDates.put(from, last);
159            }
160        }
161
162        return last;
163    }
164
165    @Override
166    public void setDateOfLastDeviceIdPublication(OmemoDevice userDevice, OmemoDevice contactsDevice, Date date) {
167        getCache(userDevice).lastDeviceIdPublicationDates.put(contactsDevice, date);
168        if (persistent != null) {
169            persistent.setDateOfLastReceivedMessage(userDevice, contactsDevice, date);
170        }
171    }
172
173    @Override
174    public Date getDateOfLastDeviceIdPublication(OmemoDevice userDevice, OmemoDevice contactsDevice) {
175        Date last = getCache(userDevice).lastDeviceIdPublicationDates.get(contactsDevice);
176
177        if (last == null && persistent != null) {
178            last = persistent.getDateOfLastDeviceIdPublication(userDevice, contactsDevice);
179            if (last != null) {
180                getCache(userDevice).lastDeviceIdPublicationDates.put(contactsDevice, last);
181            }
182        }
183
184        return last;
185    }
186
187    @Override
188    public void setDateOfLastSignedPreKeyRenewal(OmemoDevice userDevice, Date date) {
189        getCache(userDevice).lastRenewalDate = date;
190        if (persistent != null) {
191            persistent.setDateOfLastSignedPreKeyRenewal(userDevice, date);
192        }
193    }
194
195    @Override
196    public Date getDateOfLastSignedPreKeyRenewal(OmemoDevice userDevice) {
197        Date lastRenewal = getCache(userDevice).lastRenewalDate;
198
199        if (lastRenewal == null && persistent != null) {
200            lastRenewal = persistent.getDateOfLastSignedPreKeyRenewal(userDevice);
201            if (lastRenewal != null) {
202                getCache(userDevice).lastRenewalDate = lastRenewal;
203            }
204        }
205
206        return lastRenewal;
207    }
208
209    @Override
210    public T_PreKey loadOmemoPreKey(OmemoDevice userDevice, int preKeyId) {
211        T_PreKey preKey = getCache(userDevice).preKeys.get(preKeyId);
212
213        if (preKey == null && persistent != null) {
214            preKey = persistent.loadOmemoPreKey(userDevice, preKeyId);
215            if (preKey != null) {
216                getCache(userDevice).preKeys.put(preKeyId, preKey);
217            }
218        }
219
220        return preKey;
221    }
222
223    @Override
224    public void storeOmemoPreKey(OmemoDevice userDevice, int preKeyId, T_PreKey t_preKey) {
225        getCache(userDevice).preKeys.put(preKeyId, t_preKey);
226        if (persistent != null) {
227            persistent.storeOmemoPreKey(userDevice, preKeyId, t_preKey);
228        }
229    }
230
231    @Override
232    public void removeOmemoPreKey(OmemoDevice userDevice, int preKeyId) {
233        getCache(userDevice).preKeys.remove(preKeyId);
234        if (persistent != null) {
235            persistent.removeOmemoPreKey(userDevice, preKeyId);
236        }
237    }
238
239    @Override
240    public TreeMap<Integer, T_PreKey> loadOmemoPreKeys(OmemoDevice userDevice) {
241        TreeMap<Integer, T_PreKey> preKeys = getCache(userDevice).preKeys;
242
243        if (preKeys.isEmpty() && persistent != null) {
244            preKeys.putAll(persistent.loadOmemoPreKeys(userDevice));
245        }
246
247        return new TreeMap<>(preKeys);
248    }
249
250    @Override
251    public T_SigPreKey loadOmemoSignedPreKey(OmemoDevice userDevice, int signedPreKeyId) {
252        T_SigPreKey sigPreKey = getCache(userDevice).signedPreKeys.get(signedPreKeyId);
253
254        if (sigPreKey == null && persistent != null) {
255            sigPreKey = persistent.loadOmemoSignedPreKey(userDevice, signedPreKeyId);
256            if (sigPreKey != null) {
257                getCache(userDevice).signedPreKeys.put(signedPreKeyId, sigPreKey);
258            }
259        }
260
261        return sigPreKey;
262    }
263
264    @Override
265    public TreeMap<Integer, T_SigPreKey> loadOmemoSignedPreKeys(OmemoDevice userDevice) {
266        TreeMap<Integer, T_SigPreKey> sigPreKeys = getCache(userDevice).signedPreKeys;
267
268        if (sigPreKeys.isEmpty() && persistent != null) {
269            sigPreKeys.putAll(persistent.loadOmemoSignedPreKeys(userDevice));
270        }
271
272        return new TreeMap<>(sigPreKeys);
273    }
274
275    @Override
276    public void storeOmemoSignedPreKey(OmemoDevice userDevice,
277                                       int signedPreKeyId,
278                                       T_SigPreKey signedPreKey) {
279        getCache(userDevice).signedPreKeys.put(signedPreKeyId, signedPreKey);
280        if (persistent != null) {
281            persistent.storeOmemoSignedPreKey(userDevice, signedPreKeyId, signedPreKey);
282        }
283    }
284
285    @Override
286    public void removeOmemoSignedPreKey(OmemoDevice userDevice, int signedPreKeyId) {
287        getCache(userDevice).signedPreKeys.remove(signedPreKeyId);
288        if (persistent != null) {
289            persistent.removeOmemoSignedPreKey(userDevice, signedPreKeyId);
290        }
291    }
292
293    @Override
294    public T_Sess loadRawSession(OmemoDevice userDevice, OmemoDevice contactsDevice) {
295        HashMap<Integer, T_Sess> contactSessions = getCache(userDevice).sessions.get(contactsDevice.getJid());
296        if (contactSessions == null) {
297            contactSessions = new HashMap<>();
298            getCache(userDevice).sessions.put(contactsDevice.getJid(), contactSessions);
299        }
300
301        T_Sess session = contactSessions.get(contactsDevice.getDeviceId());
302        if (session == null && persistent != null) {
303            session = persistent.loadRawSession(userDevice, contactsDevice);
304            if (session != null) {
305                contactSessions.put(contactsDevice.getDeviceId(), session);
306            }
307        }
308
309        return session;
310    }
311
312    @Override
313    public HashMap<Integer, T_Sess> loadAllRawSessionsOf(OmemoDevice userDevice, BareJid contact) {
314        HashMap<Integer, T_Sess> sessions = getCache(userDevice).sessions.get(contact);
315        if (sessions == null) {
316            sessions = new HashMap<>();
317            getCache(userDevice).sessions.put(contact, sessions);
318        }
319
320        if (sessions.isEmpty() && persistent != null) {
321            sessions.putAll(persistent.loadAllRawSessionsOf(userDevice, contact));
322        }
323
324        return new HashMap<>(sessions);
325    }
326
327    @Override
328    public void storeRawSession(OmemoDevice userDevice, OmemoDevice contactsDevicece, T_Sess session) {
329        HashMap<Integer, T_Sess> sessions = getCache(userDevice).sessions.get(contactsDevicece.getJid());
330        if (sessions == null) {
331            sessions = new HashMap<>();
332            getCache(userDevice).sessions.put(contactsDevicece.getJid(), sessions);
333        }
334
335        sessions.put(contactsDevicece.getDeviceId(), session);
336        if (persistent != null) {
337            persistent.storeRawSession(userDevice, contactsDevicece, session);
338        }
339    }
340
341    @Override
342    public void removeRawSession(OmemoDevice userDevice, OmemoDevice contactsDevice) {
343        HashMap<Integer, T_Sess> sessions = getCache(userDevice).sessions.get(contactsDevice.getJid());
344        if (sessions != null) {
345            sessions.remove(contactsDevice.getDeviceId());
346        }
347
348        if (persistent != null) {
349            persistent.removeRawSession(userDevice, contactsDevice);
350        }
351    }
352
353    @Override
354    public void removeAllRawSessionsOf(OmemoDevice userDevice, BareJid contact) {
355        getCache(userDevice).sessions.remove(contact);
356        if (persistent != null) {
357            persistent.removeAllRawSessionsOf(userDevice, contact);
358        }
359    }
360
361    @Override
362    public boolean containsRawSession(OmemoDevice userDevice, OmemoDevice contactsDevice) {
363        HashMap<Integer, T_Sess> sessions = getCache(userDevice).sessions.get(contactsDevice.getJid());
364
365        return (sessions != null && sessions.get(contactsDevice.getDeviceId()) != null) ||
366                (persistent != null && persistent.containsRawSession(userDevice, contactsDevice));
367    }
368
369    @Override
370    public OmemoCachedDeviceList loadCachedDeviceList(OmemoDevice userDevice, BareJid contact) {
371        OmemoCachedDeviceList list = getCache(userDevice).deviceLists.get(contact);
372
373        if (list == null && persistent != null) {
374            list = persistent.loadCachedDeviceList(userDevice, contact);
375            if (list != null) {
376                getCache(userDevice).deviceLists.put(contact, list);
377            }
378        }
379
380        return list == null ? new OmemoCachedDeviceList() : new OmemoCachedDeviceList(list);
381    }
382
383    @Override
384    public void storeCachedDeviceList(OmemoDevice userDevice,
385                                      BareJid contact,
386                                      OmemoCachedDeviceList deviceList) {
387        getCache(userDevice).deviceLists.put(contact, new OmemoCachedDeviceList(deviceList));
388
389        if (persistent != null) {
390            persistent.storeCachedDeviceList(userDevice, contact, deviceList);
391        }
392    }
393
394    @Override
395    public void purgeOwnDeviceKeys(OmemoDevice userDevice) {
396        caches.remove(userDevice);
397
398        if (persistent != null) {
399            persistent.purgeOwnDeviceKeys(userDevice);
400        }
401    }
402
403    @Override
404    public OmemoKeyUtil<T_IdKeyPair, T_IdKey, T_PreKey, T_SigPreKey, T_Sess, T_ECPub, T_Bundle>
405    keyUtil() {
406        if (persistent != null) {
407            return persistent.keyUtil();
408        } else {
409            return keyUtil;
410        }
411    }
412
413    /**
414     * Return the {@link KeyCache} object of an {@link OmemoManager}.
415     * @param device
416     * @return
417     */
418    private KeyCache<T_IdKeyPair, T_IdKey, T_PreKey, T_SigPreKey, T_Sess> getCache(OmemoDevice device) {
419        KeyCache<T_IdKeyPair, T_IdKey, T_PreKey, T_SigPreKey, T_Sess> cache = caches.get(device);
420        if (cache == null) {
421            cache = new KeyCache<>();
422            caches.put(device, cache);
423        }
424        return cache;
425    }
426
427    /**
428     * Cache that stores values for an {@link OmemoManager}.
429     * @param <T_IdKeyPair>
430     * @param <T_IdKey>
431     * @param <T_PreKey>
432     * @param <T_SigPreKey>
433     * @param <T_Sess>
434     */
435    private static class KeyCache<T_IdKeyPair, T_IdKey, T_PreKey, T_SigPreKey, T_Sess> {
436        private T_IdKeyPair identityKeyPair;
437        private final TreeMap<Integer, T_PreKey> preKeys = new TreeMap<>();
438        private final TreeMap<Integer, T_SigPreKey> signedPreKeys = new TreeMap<>();
439        private final HashMap<BareJid, HashMap<Integer, T_Sess>> sessions = new HashMap<>();
440        private final HashMap<OmemoDevice, T_IdKey> identityKeys = new HashMap<>();
441        private final HashMap<OmemoDevice, Date> lastMessagesDates = new HashMap<>();
442        private final HashMap<OmemoDevice, Date> lastDeviceIdPublicationDates = new HashMap<>();
443        private final HashMap<BareJid, OmemoCachedDeviceList> deviceLists = new HashMap<>();
444        private Date lastRenewalDate = null;
445    }
446}