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}