001/** 002 * 003 * Copyright 2017 Paul Schaub, 2019 Florian Schmaus. 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.io.DataInputStream; 020import java.io.DataOutputStream; 021import java.io.EOFException; 022import java.io.File; 023import java.io.FileInputStream; 024import java.io.FileOutputStream; 025import java.io.IOException; 026import java.util.Collections; 027import java.util.Date; 028import java.util.HashMap; 029import java.util.HashSet; 030import java.util.Set; 031import java.util.SortedSet; 032import java.util.Stack; 033import java.util.TreeMap; 034import java.util.TreeSet; 035import java.util.logging.Level; 036import java.util.logging.Logger; 037 038import org.jivesoftware.smack.util.stringencoder.BareJidEncoder; 039 040import org.jivesoftware.smackx.omemo.exceptions.CorruptedOmemoKeyException; 041import org.jivesoftware.smackx.omemo.internal.OmemoCachedDeviceList; 042import org.jivesoftware.smackx.omemo.internal.OmemoDevice; 043 044import org.jxmpp.jid.BareJid; 045 046/** 047 * Like a rocket! 048 * Implementation of the {@link OmemoStore} class that uses plain files for storage. 049 * 050 * @author Paul Schaub 051 */ 052public abstract class FileBasedOmemoStore<T_IdKeyPair, T_IdKey, T_PreKey, T_SigPreKey, T_Sess, T_Addr, T_ECPub, T_Bundle, T_Ciph> 053 extends OmemoStore<T_IdKeyPair, T_IdKey, T_PreKey, T_SigPreKey, T_Sess, T_Addr, T_ECPub, T_Bundle, T_Ciph> { 054 055 private final FileHierarchy hierarchy; 056 private static final Logger LOGGER = Logger.getLogger(FileBasedOmemoStore.class.getName()); 057 private static BareJidEncoder bareJidEncoder = new BareJidEncoder.UrlSafeEncoder(); 058 059 public FileBasedOmemoStore(File basePath) { 060 super(); 061 if (basePath == null) { 062 throw new IllegalStateException("No FileBasedOmemoStoreDefaultPath set in OmemoConfiguration."); 063 } 064 this.hierarchy = new FileHierarchy(basePath); 065 } 066 067 @Override 068 public T_IdKeyPair loadOmemoIdentityKeyPair(OmemoDevice userDevice) 069 throws CorruptedOmemoKeyException, IOException { 070 File identityKeyPairPath = hierarchy.getIdentityKeyPairPath(userDevice); 071 return keyUtil().identityKeyPairFromBytes(readBytes(identityKeyPairPath)); 072 } 073 074 @Override 075 public void storeOmemoIdentityKeyPair(OmemoDevice userDevice, T_IdKeyPair identityKeyPair) throws IOException { 076 File identityKeyPairPath = hierarchy.getIdentityKeyPairPath(userDevice); 077 writeBytes(identityKeyPairPath, keyUtil().identityKeyPairToBytes(identityKeyPair)); 078 } 079 080 @Override 081 public void removeOmemoIdentityKeyPair(OmemoDevice userDevice) { 082 File identityKeyPairPath = hierarchy.getIdentityKeyPairPath(userDevice); 083 if (!identityKeyPairPath.delete()) { 084 LOGGER.log(Level.WARNING, "Could not delete OMEMO IdentityKeyPair " + identityKeyPairPath.getAbsolutePath()); 085 } 086 } 087 088 @Override 089 public T_IdKey loadOmemoIdentityKey(OmemoDevice userDevice, OmemoDevice contactsDevice) 090 throws CorruptedOmemoKeyException, IOException { 091 File identityKeyPath = hierarchy.getContactsIdentityKeyPath(userDevice, contactsDevice); 092 byte[] bytes = readBytes(identityKeyPath); 093 return bytes != null ? keyUtil().identityKeyFromBytes(bytes) : null; 094 } 095 096 @Override 097 public void storeOmemoIdentityKey(OmemoDevice userDevice, OmemoDevice contactsDevice, T_IdKey t_idKey) throws IOException { 098 File identityKeyPath = hierarchy.getContactsIdentityKeyPath(userDevice, contactsDevice); 099 writeBytes(identityKeyPath, keyUtil().identityKeyToBytes(t_idKey)); 100 } 101 102 @Override 103 public void removeOmemoIdentityKey(OmemoDevice userDevice, OmemoDevice contactsDevice) { 104 File identityKeyPath = hierarchy.getContactsIdentityKeyPath(userDevice, contactsDevice); 105 if (!identityKeyPath.delete()) { 106 LOGGER.log(Level.WARNING, "Could not delete OMEMO identityKey " + identityKeyPath.getAbsolutePath()); 107 } 108 } 109 110 @Override 111 public SortedSet<Integer> localDeviceIdsOf(BareJid localUser) { 112 SortedSet<Integer> deviceIds = new TreeSet<>(); 113 File userDir = hierarchy.getUserDirectory(localUser); 114 File[] list = userDir.listFiles(); 115 for (File d : list != null ? list : new File[] {}) { 116 if (d.isDirectory()) { 117 try { 118 deviceIds.add(Integer.parseInt(d.getName())); 119 } catch (NumberFormatException e) { 120 // ignore 121 } 122 } 123 } 124 return deviceIds; 125 } 126 127 @Override 128 @SuppressWarnings("JavaUtilDate") 129 public void setDateOfLastReceivedMessage(OmemoDevice userDevice, OmemoDevice contactsDevice, Date date) throws IOException { 130 File lastMessageReceived = hierarchy.getLastMessageReceivedDatePath(userDevice, contactsDevice); 131 writeLong(lastMessageReceived, date.getTime()); 132 } 133 134 @Override 135 @SuppressWarnings("JavaUtilDate") 136 public Date getDateOfLastReceivedMessage(OmemoDevice userDevice, OmemoDevice contactsDevice) throws IOException { 137 File lastMessageReceived = hierarchy.getLastMessageReceivedDatePath(userDevice, contactsDevice); 138 Long date = readLong(lastMessageReceived); 139 return date != null ? new Date(date) : null; 140 } 141 142 @Override 143 @SuppressWarnings("JavaUtilDate") 144 public void setDateOfLastDeviceIdPublication(OmemoDevice userDevice, OmemoDevice contactsDevice, Date date) throws IOException { 145 File lastDeviceIdPublished = hierarchy.getLastDeviceIdPublicationDatePath(userDevice, contactsDevice); 146 writeLong(lastDeviceIdPublished, date.getTime()); 147 } 148 149 @Override 150 @SuppressWarnings("JavaUtilDate") 151 public Date getDateOfLastDeviceIdPublication(OmemoDevice userDevice, OmemoDevice contactsDevice) throws IOException { 152 File lastDeviceIdPublished = hierarchy.getLastDeviceIdPublicationDatePath(userDevice, contactsDevice); 153 Long date = readLong(lastDeviceIdPublished); 154 return date != null ? new Date(date) : null; 155 } 156 157 @Override 158 @SuppressWarnings("JavaUtilDate") 159 public void setDateOfLastSignedPreKeyRenewal(OmemoDevice userDevice, Date date) throws IOException { 160 File lastSignedPreKeyRenewal = hierarchy.getLastSignedPreKeyRenewal(userDevice); 161 writeLong(lastSignedPreKeyRenewal, date.getTime()); 162 } 163 164 @Override 165 @SuppressWarnings("JavaUtilDate") 166 public Date getDateOfLastSignedPreKeyRenewal(OmemoDevice userDevice) throws IOException { 167 File lastSignedPreKeyRenewal = hierarchy.getLastSignedPreKeyRenewal(userDevice); 168 Long date = readLong(lastSignedPreKeyRenewal); 169 return date != null ? new Date(date) : null; 170 } 171 172 @Override 173 public T_PreKey loadOmemoPreKey(OmemoDevice userDevice, int preKeyId) throws IOException { 174 File preKeyPath = hierarchy.getPreKeyPath(userDevice, preKeyId); 175 byte[] bytes = readBytes(preKeyPath); 176 177 if (bytes != null) { 178 try { 179 return keyUtil().preKeyFromBytes(bytes); 180 } catch (IOException e) { 181 LOGGER.log(Level.WARNING, "Could not deserialize preKey from bytes.", e); 182 } 183 } 184 185 return null; 186 } 187 188 @Override 189 public void storeOmemoPreKey(OmemoDevice userDevice, int preKeyId, T_PreKey t_preKey) throws IOException { 190 File preKeyPath = hierarchy.getPreKeyPath(userDevice, preKeyId); 191 writeBytes(preKeyPath, keyUtil().preKeyToBytes(t_preKey)); 192 } 193 194 @Override 195 public void removeOmemoPreKey(OmemoDevice userDevice, int preKeyId) { 196 File preKeyPath = hierarchy.getPreKeyPath(userDevice, preKeyId); 197 if (!preKeyPath.delete()) { 198 LOGGER.log(Level.WARNING, "Deleting OMEMO preKey " + preKeyPath.getAbsolutePath() + " failed."); 199 } 200 } 201 202 @Override 203 @SuppressWarnings("NonApiType") 204 public TreeMap<Integer, T_PreKey> loadOmemoPreKeys(OmemoDevice userDevice) throws IOException { 205 File preKeyDirectory = hierarchy.getPreKeysDirectory(userDevice); 206 TreeMap<Integer, T_PreKey> preKeys = new TreeMap<>(); 207 208 if (preKeyDirectory == null) { 209 return preKeys; 210 } 211 212 File[] keys = preKeyDirectory.listFiles(); 213 214 for (File f : keys != null ? keys : new File[0]) { 215 byte[] bytes = readBytes(f); 216 if (bytes != null) { 217 try { 218 T_PreKey p = keyUtil().preKeyFromBytes(bytes); 219 preKeys.put(Integer.parseInt(f.getName()), p); 220 } catch (IOException e) { 221 LOGGER.log(Level.WARNING, "Could not deserialize preKey from bytes.", e); 222 } 223 } 224 } 225 226 return preKeys; 227 } 228 229 @Override 230 public T_SigPreKey loadOmemoSignedPreKey(OmemoDevice userDevice, int signedPreKeyId) throws IOException { 231 File signedPreKeyPath = new File(hierarchy.getSignedPreKeysDirectory(userDevice), Integer.toString(signedPreKeyId)); 232 byte[] bytes = readBytes(signedPreKeyPath); 233 if (bytes != null) { 234 try { 235 return keyUtil().signedPreKeyFromBytes(bytes); 236 } catch (IOException e) { 237 LOGGER.log(Level.WARNING, "Could not deserialize signed preKey from bytes.", e); 238 } 239 } 240 return null; 241 } 242 243 @Override 244 @SuppressWarnings("NonApiType") 245 public TreeMap<Integer, T_SigPreKey> loadOmemoSignedPreKeys(OmemoDevice userDevice) throws IOException { 246 File signedPreKeysDirectory = hierarchy.getSignedPreKeysDirectory(userDevice); 247 TreeMap<Integer, T_SigPreKey> signedPreKeys = new TreeMap<>(); 248 249 if (signedPreKeysDirectory == null) { 250 return signedPreKeys; 251 } 252 253 File[] keys = signedPreKeysDirectory.listFiles(); 254 255 for (File f : keys != null ? keys : new File[0]) { 256 byte[] bytes = readBytes(f); 257 if (bytes != null) { 258 try { 259 T_SigPreKey p = keyUtil().signedPreKeyFromBytes(bytes); 260 signedPreKeys.put(Integer.parseInt(f.getName()), p); 261 } catch (IOException e) { 262 LOGGER.log(Level.WARNING, "Could not deserialize signed preKey.", e); 263 } 264 } 265 } 266 267 return signedPreKeys; 268 } 269 270 @Override 271 public void storeOmemoSignedPreKey(OmemoDevice userDevice, 272 int signedPreKeyId, 273 T_SigPreKey signedPreKey) throws IOException { 274 File signedPreKeyPath = new File(hierarchy.getSignedPreKeysDirectory(userDevice), Integer.toString(signedPreKeyId)); 275 writeBytes(signedPreKeyPath, keyUtil().signedPreKeyToBytes(signedPreKey)); 276 } 277 278 @Override 279 public void removeOmemoSignedPreKey(OmemoDevice userDevice, int signedPreKeyId) { 280 File signedPreKeyPath = new File(hierarchy.getSignedPreKeysDirectory(userDevice), Integer.toString(signedPreKeyId)); 281 if (!signedPreKeyPath.delete()) { 282 LOGGER.log(Level.WARNING, "Deleting signed OMEMO preKey " + signedPreKeyPath.getAbsolutePath() + " failed."); 283 } 284 } 285 286 @Override 287 public T_Sess loadRawSession(OmemoDevice userDevice, OmemoDevice contactsDevice) throws IOException { 288 File sessionPath = hierarchy.getContactsSessionPath(userDevice, contactsDevice); 289 byte[] bytes = readBytes(sessionPath); 290 if (bytes != null) { 291 try { 292 return keyUtil().rawSessionFromBytes(bytes); 293 } catch (IOException e) { 294 LOGGER.log(Level.WARNING, "Could not deserialize raw session.", e); 295 } 296 } 297 return null; 298 } 299 300 @Override 301 @SuppressWarnings("NonApiType") 302 public HashMap<Integer, T_Sess> loadAllRawSessionsOf(OmemoDevice userDevice, BareJid contact) throws IOException { 303 File contactsDirectory = hierarchy.getContactsDir(userDevice, contact); 304 HashMap<Integer, T_Sess> sessions = new HashMap<>(); 305 String[] devices = contactsDirectory.list(); 306 307 for (String deviceId : devices != null ? devices : new String[0]) { 308 int id; 309 try { 310 id = Integer.parseInt(deviceId); 311 } catch (NumberFormatException e) { 312 continue; 313 } 314 OmemoDevice device = new OmemoDevice(contact, id); 315 File session = hierarchy.getContactsSessionPath(userDevice, device); 316 317 byte[] bytes = readBytes(session); 318 319 if (bytes != null) { 320 try { 321 T_Sess s = keyUtil().rawSessionFromBytes(bytes); 322 sessions.put(id, s); 323 } catch (IOException e) { 324 LOGGER.log(Level.WARNING, "Could not deserialize raw session.", e); 325 } 326 } 327 328 } 329 return sessions; 330 } 331 332 @Override 333 public void storeRawSession(OmemoDevice userDevice, OmemoDevice contactsDevice, T_Sess session) throws IOException { 334 File sessionPath = hierarchy.getContactsSessionPath(userDevice, contactsDevice); 335 writeBytes(sessionPath, keyUtil().rawSessionToBytes(session)); 336 } 337 338 @Override 339 public void removeRawSession(OmemoDevice userDevice, OmemoDevice contactsDevice) { 340 File sessionPath = hierarchy.getContactsSessionPath(userDevice, contactsDevice); 341 if (!sessionPath.delete()) { 342 LOGGER.log(Level.WARNING, "Deleting raw OMEMO session " + sessionPath.getAbsolutePath() + " failed."); 343 } 344 } 345 346 @Override 347 public void removeAllRawSessionsOf(OmemoDevice userDevice, BareJid contact) { 348 File contactsDirectory = hierarchy.getContactsDir(userDevice, contact); 349 String[] devices = contactsDirectory.list(); 350 351 for (String deviceId : devices != null ? devices : new String[0]) { 352 int id = Integer.parseInt(deviceId); 353 OmemoDevice device = new OmemoDevice(contact, id); 354 File session = hierarchy.getContactsSessionPath(userDevice, device); 355 if (!session.delete()) { 356 LOGGER.log(Level.WARNING, "Deleting raw OMEMO session " + session.getAbsolutePath() + "failed."); 357 } 358 } 359 } 360 361 @Override 362 public boolean containsRawSession(OmemoDevice userDevice, OmemoDevice contactsDevice) { 363 File session = hierarchy.getContactsSessionPath(userDevice, contactsDevice); 364 return session.exists(); 365 } 366 367 @Override 368 public void storeOmemoMessageCounter(OmemoDevice userDevice, OmemoDevice contactsDevice, int counter) throws IOException { 369 File messageCounterFile = hierarchy.getDevicesMessageCounterPath(userDevice, contactsDevice); 370 writeIntegers(messageCounterFile, Collections.singleton(counter)); 371 } 372 373 @Override 374 public int loadOmemoMessageCounter(OmemoDevice userDevice, OmemoDevice contactsDevice) throws IOException { 375 File messageCounterFile = hierarchy.getDevicesMessageCounterPath(userDevice, contactsDevice); 376 Set<Integer> integers = readIntegers(messageCounterFile); 377 378 if (integers == null || integers.isEmpty()) { 379 return 0; 380 } 381 382 return integers.iterator().next(); 383 } 384 385 @Override 386 public OmemoCachedDeviceList loadCachedDeviceList(OmemoDevice userDevice, BareJid contact) throws IOException { 387 OmemoCachedDeviceList cachedDeviceList = new OmemoCachedDeviceList(); 388 389 if (contact == null) { 390 throw new IllegalArgumentException("Contact can not be null."); 391 } 392 393 // active 394 File activeDevicesPath = hierarchy.getContactsActiveDevicesPath(userDevice, contact); 395 Set<Integer> active = readIntegers(activeDevicesPath); 396 if (active != null) { 397 cachedDeviceList.getActiveDevices().addAll(active); 398 } 399 400 // inactive 401 File inactiveDevicesPath = hierarchy.getContactsInactiveDevicesPath(userDevice, contact); 402 Set<Integer> inactive = readIntegers(inactiveDevicesPath); 403 if (inactive != null) { 404 cachedDeviceList.getInactiveDevices().addAll(inactive); 405 } 406 407 return cachedDeviceList; 408 } 409 410 @Override 411 public void storeCachedDeviceList(OmemoDevice userDevice, 412 BareJid contact, 413 OmemoCachedDeviceList contactsDeviceList) throws IOException { 414 if (contact == null) { 415 return; 416 } 417 418 File activeDevices = hierarchy.getContactsActiveDevicesPath(userDevice, contact); 419 writeIntegers(activeDevices, contactsDeviceList.getActiveDevices()); 420 421 File inactiveDevices = hierarchy.getContactsInactiveDevicesPath(userDevice, contact); 422 writeIntegers(inactiveDevices, contactsDeviceList.getInactiveDevices()); 423 } 424 425 @Override 426 public void purgeOwnDeviceKeys(OmemoDevice userDevice) { 427 File deviceDirectory = hierarchy.getUserDeviceDirectory(userDevice); 428 deleteDirectory(deviceDirectory); 429 } 430 431 private static void writeLong(File target, long i) throws IOException { 432 if (target == null) { 433 throw new IOException("Could not write long to null-path."); 434 } 435 436 FileHierarchy.createFile(target); 437 438 try (DataOutputStream out = new DataOutputStream(new FileOutputStream(target))) { 439 out.writeLong(i); 440 } 441 } 442 443 private static Long readLong(File target) throws IOException { 444 if (target == null) { 445 throw new IOException("Could not read long from null-path."); 446 } 447 448 if (!target.exists() || !target.isFile()) { 449 return null; 450 } 451 452 try (DataInputStream in = new DataInputStream(new FileInputStream(target))) { 453 return in.readLong(); 454 } 455 } 456 457 private static void writeBytes(File target, byte[] bytes) throws IOException { 458 if (target == null) { 459 throw new IOException("Could not write bytes to null-path."); 460 } 461 462 // Create file 463 FileHierarchy.createFile(target); 464 465 try (DataOutputStream out = new DataOutputStream(new FileOutputStream(target))) { 466 out.write(bytes); 467 } 468 } 469 470 private static byte[] readBytes(File target) throws IOException { 471 if (target == null) { 472 throw new IOException("Could not read bytes from null-path."); 473 } 474 475 if (!target.exists() || !target.isFile()) { 476 return null; 477 } 478 479 byte[] b = new byte[(int) target.length()]; 480 try (DataInputStream in = new DataInputStream(new FileInputStream(target))) { 481 in.read(b); 482 } 483 484 return b; 485 } 486 487 private static void writeIntegers(File target, Set<Integer> integers) throws IOException { 488 if (target == null) { 489 throw new IOException("Could not write integers to null-path."); 490 } 491 492 FileHierarchy.createFile(target); 493 494 try (DataOutputStream out = new DataOutputStream(new FileOutputStream(target))) { 495 for (int i : integers) { 496 out.writeInt(i); 497 } 498 } 499 } 500 501 private static Set<Integer> readIntegers(File target) throws IOException { 502 if (target == null) { 503 throw new IOException("Could not write integers to null-path."); 504 } 505 506 if (!target.exists() || !target.isFile()) { 507 return null; 508 } 509 510 HashSet<Integer> integers = new HashSet<>(); 511 512 try (DataInputStream in = new DataInputStream(new FileInputStream(target))) { 513 while (true) { 514 try { 515 integers.add(in.readInt()); 516 } catch (EOFException e) { 517 break; 518 } 519 } 520 } 521 522 return integers; 523 } 524 525 /** 526 * Delete a directory with all subdirectories. 527 * @param root directory to be deleted 528 */ 529 @SuppressWarnings("JdkObsolete") 530 public static void deleteDirectory(File root) { 531 File[] currList; 532 Stack<File> stack = new Stack<>(); 533 stack.push(root); 534 while (!stack.isEmpty()) { 535 if (stack.lastElement().isDirectory()) { 536 currList = stack.lastElement().listFiles(); 537 if (currList != null && currList.length > 0) { 538 for (File curr : currList) { 539 stack.push(curr); 540 } 541 } else { 542 stack.pop().delete(); 543 } 544 } else { 545 stack.pop().delete(); 546 } 547 } 548 } 549 550 /** 551 * This class represents the directory structure of the FileBasedOmemoStore. 552 * The directory looks as follows: 553 * 554 * OMEMO_Store/ 555 * 'romeo@montague.lit'/ //Our bareJid 556 * ... 557 * 'juliet@capulet.lit'/ //Our other bareJid 558 * '13371234'/ //deviceId 559 * identityKeyPair //Our identityKeyPair 560 * lastSignedPreKeyRenewal //Date of when the signedPreKey was last renewed. 561 * preKeys/ //Our preKeys 562 * '1' 563 * '2' 564 * ... 565 * signedPreKeys/ //Our signedPreKeys 566 * '1' 567 * '2' 568 * ... 569 * contacts/ 570 * 'romeo@capulet.lit'/ //Juliets contact Romeo 571 * activeDevice //List of Romeos active devices 572 * inactiveDevices //List of his inactive devices 573 * 'deviceId'/ //Romeos deviceId 574 * identityKey //Romeos identityKey 575 * session //Our session with romeo 576 * trust //Records about the trust in romeos device 577 * (lastReceivedMessageDate) //Only, for our own other devices: 578 * //date of the last received message 579 * 580 */ 581 public static class FileHierarchy { 582 583 static final String STORE = "OMEMO_Store"; 584 static final String CONTACTS = "contacts"; 585 static final String IDENTITY_KEY = "identityKey"; 586 static final String IDENTITY_KEY_PAIR = "identityKeyPair"; 587 static final String PRE_KEYS = "preKeys"; 588 static final String LAST_MESSAGE_RECEVIED_DATE = "lastMessageReceivedDate"; 589 static final String LAST_DEVICEID_PUBLICATION_DATE = "lastDeviceIdPublicationDate"; 590 static final String SIGNED_PRE_KEYS = "signedPreKeys"; 591 static final String LAST_SIGNED_PRE_KEY_RENEWAL = "lastSignedPreKeyRenewal"; 592 static final String SESSION = "session"; 593 static final String DEVICE_LIST_ACTIVE = "activeDevices"; 594 static final String DEVICE_LIST_INAVTIVE = "inactiveDevices"; 595 static final String MESSAGE_COUNTER = "messageCounter"; 596 597 File basePath; 598 599 FileHierarchy(File basePath) { 600 this.basePath = basePath; 601 basePath.mkdirs(); 602 } 603 604 File getStoreDirectory() { 605 return createDirectory(basePath, STORE); 606 } 607 608 File getUserDirectory(OmemoDevice userDevice) { 609 return getUserDirectory(userDevice.getJid()); 610 } 611 612 File getUserDirectory(BareJid bareJid) { 613 return createDirectory(getStoreDirectory(), bareJidEncoder.encode(bareJid)); 614 } 615 616 File getUserDeviceDirectory(OmemoDevice userDevice) { 617 return createDirectory(getUserDirectory(userDevice.getJid()), 618 Integer.toString(userDevice.getDeviceId())); 619 } 620 621 File getContactsDir(OmemoDevice userDevice) { 622 return createDirectory(getUserDeviceDirectory(userDevice), CONTACTS); 623 } 624 625 File getContactsDir(OmemoDevice userDevice, BareJid contact) { 626 return createDirectory(getContactsDir(userDevice), bareJidEncoder.encode(contact)); 627 } 628 629 File getContactsDir(OmemoDevice userDevice, OmemoDevice contactsDevice) { 630 return createDirectory(getContactsDir(userDevice, contactsDevice.getJid()), 631 Integer.toString(contactsDevice.getDeviceId())); 632 } 633 634 File getIdentityKeyPairPath(OmemoDevice userDevice) { 635 return new File(getUserDeviceDirectory(userDevice), IDENTITY_KEY_PAIR); 636 } 637 638 File getPreKeysDirectory(OmemoDevice userDevice) { 639 return createDirectory(getUserDeviceDirectory(userDevice), PRE_KEYS); 640 } 641 642 File getPreKeyPath(OmemoDevice userDevice, int preKeyId) { 643 return new File(getPreKeysDirectory(userDevice), Integer.toString(preKeyId)); 644 } 645 646 File getLastMessageReceivedDatePath(OmemoDevice userDevice, OmemoDevice device) { 647 return new File(getContactsDir(userDevice, device), LAST_MESSAGE_RECEVIED_DATE); 648 } 649 650 File getLastDeviceIdPublicationDatePath(OmemoDevice userDevice, OmemoDevice device) { 651 return new File(getContactsDir(userDevice, device), LAST_DEVICEID_PUBLICATION_DATE); 652 } 653 654 File getSignedPreKeysDirectory(OmemoDevice userDevice) { 655 return createDirectory(getUserDeviceDirectory(userDevice), SIGNED_PRE_KEYS); 656 } 657 658 File getLastSignedPreKeyRenewal(OmemoDevice userDevice) { 659 return new File(getUserDeviceDirectory(userDevice), LAST_SIGNED_PRE_KEY_RENEWAL); 660 } 661 662 File getContactsIdentityKeyPath(OmemoDevice userDevice, OmemoDevice contactsDevice) { 663 return new File(getContactsDir(userDevice, contactsDevice), IDENTITY_KEY); 664 665 } 666 667 File getContactsSessionPath(OmemoDevice userDevice, OmemoDevice contactsDevice) { 668 return new File(getContactsDir(userDevice, contactsDevice), SESSION); 669 } 670 671 File getContactsActiveDevicesPath(OmemoDevice userDevice, BareJid contact) { 672 return new File(getContactsDir(userDevice, contact), DEVICE_LIST_ACTIVE); 673 } 674 675 File getContactsInactiveDevicesPath(OmemoDevice userDevice, BareJid contact) { 676 return new File(getContactsDir(userDevice, contact), DEVICE_LIST_INAVTIVE); 677 } 678 679 File getDevicesMessageCounterPath(OmemoDevice userDevice, OmemoDevice otherDevice) { 680 return new File(getContactsDir(userDevice, otherDevice), MESSAGE_COUNTER); 681 } 682 683 private static File createFile(File f) throws IOException { 684 File p = f.getParentFile(); 685 createDirectory(p); 686 f.createNewFile(); 687 return f; 688 689 } 690 691 private static File createDirectory(File dir, String subdir) { 692 File f = new File(dir, subdir); 693 return createDirectory(f); 694 } 695 696 private static File createDirectory(File f) { 697 if (f.exists() && f.isDirectory()) { 698 return f; 699 } 700 701 f.mkdirs(); 702 return f; 703 } 704 } 705 706 /** 707 * Convert {@link BareJid BareJids} to Strings using the legacy {@link BareJid#toString()} method instead of the 708 * proper, url safe {@link BareJid#asUrlEncodedString()} method. 709 * While it is highly advised to use the new format, you can use this method to stay backwards compatible to data 710 * sets created by the old implementation. 711 */ 712 @SuppressWarnings("deprecation") 713 public static void useLegacyBareJidEncoding() { 714 bareJidEncoder = new BareJidEncoder.LegacyEncoder(); 715 } 716}