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