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.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.Date; 027import java.util.HashMap; 028import java.util.HashSet; 029import java.util.Set; 030import java.util.Stack; 031import java.util.logging.Level; 032import java.util.logging.Logger; 033 034import org.jivesoftware.smack.util.StringUtils; 035 036import org.jivesoftware.smackx.omemo.exceptions.CorruptedOmemoKeyException; 037import org.jivesoftware.smackx.omemo.internal.CachedDeviceList; 038import org.jivesoftware.smackx.omemo.internal.OmemoDevice; 039 040import org.jxmpp.jid.BareJid; 041 042/** 043 * Like a rocket! 044 * 045 * @author Paul Schaub 046 */ 047public abstract class FileBasedOmemoStore<T_IdKeyPair, T_IdKey, T_PreKey, T_SigPreKey, T_Sess, T_Addr, T_ECPub, T_Bundle, T_Ciph> 048 extends OmemoStore<T_IdKeyPair, T_IdKey, T_PreKey, T_SigPreKey, T_Sess, T_Addr, T_ECPub, T_Bundle, T_Ciph> { 049 050 private static final Logger LOGGER = Logger.getLogger(FileBasedOmemoStore.class.getSimpleName()); 051 private final FileHierarchy hierarchy; 052 053 public FileBasedOmemoStore() { 054 this(OmemoConfiguration.getFileBasedOmemoStoreDefaultPath()); 055 } 056 057 public FileBasedOmemoStore(File basePath) { 058 super(); 059 if (basePath == null) { 060 throw new IllegalStateException("No FileBasedOmemoStoreDefaultPath set in OmemoConfiguration."); 061 } 062 this.hierarchy = new FileHierarchy(basePath); 063 } 064 065 @Override 066 public boolean isFreshInstallation(OmemoManager omemoManager) { 067 File userDirectory = hierarchy.getUserDeviceDirectory(omemoManager); 068 File[] files = userDirectory.listFiles(); 069 return files == null || files.length == 0; 070 } 071 072 @Override 073 public int getDefaultDeviceId(BareJid user) { 074 try { 075 return readInt(hierarchy.getDefaultDeviceIdPath(user)); 076 } catch (IOException e) { 077 return -1; 078 } 079 } 080 081 @Override 082 public void setDefaultDeviceId(BareJid user, int defaultDeviceId) { 083 File defaultDeviceIdPath = hierarchy.getDefaultDeviceIdPath(user); 084 085 if (defaultDeviceIdPath == null) { 086 LOGGER.log(Level.SEVERE, "defaultDeviceIdPath is null!"); 087 } 088 089 try { 090 writeInt(defaultDeviceIdPath, defaultDeviceId); 091 } catch (IOException e) { 092 LOGGER.log(Level.SEVERE, "Could not write defaultDeviceId: " + e, e); 093 } 094 } 095 096 @Override 097 public int loadLastPreKeyId(OmemoManager omemoManager) { 098 try { 099 int l = readInt(hierarchy.getLastPreKeyIdPath(omemoManager)); 100 return l == -1 ? 0 : l; 101 } catch (IOException e) { 102 return 0; 103 } 104 } 105 106 @Override 107 public void storeLastPreKeyId(OmemoManager omemoManager, int currentPreKeyId) { 108 try { 109 writeInt(hierarchy.getLastPreKeyIdPath(omemoManager), currentPreKeyId); 110 } catch (IOException e) { 111 LOGGER.log(Level.SEVERE, "Could not write lastPreKeyId: " + e, e); 112 } 113 } 114 115 @Override 116 public T_IdKeyPair loadOmemoIdentityKeyPair(OmemoManager omemoManager) throws CorruptedOmemoKeyException { 117 File identityKeyPairPath = hierarchy.getIdentityKeyPairPath(omemoManager); 118 try { 119 byte[] bytes = readBytes(identityKeyPairPath); 120 return bytes != null ? keyUtil().identityKeyPairFromBytes(bytes) : null; 121 } catch (IOException e) { 122 return null; 123 } 124 } 125 126 @Override 127 public void storeOmemoIdentityKeyPair(OmemoManager omemoManager, T_IdKeyPair identityKeyPair) { 128 File identityKeyPairPath = hierarchy.getIdentityKeyPairPath(omemoManager); 129 try { 130 writeBytes(identityKeyPairPath, keyUtil().identityKeyPairToBytes(identityKeyPair)); 131 } catch (IOException e) { 132 LOGGER.log(Level.SEVERE, "Could not write omemoIdentityKeyPair: " + e, e); 133 } 134 } 135 136 @Override 137 public T_IdKey loadOmemoIdentityKey(OmemoManager omemoManager, OmemoDevice device) throws CorruptedOmemoKeyException { 138 File identityKeyPath = hierarchy.getContactsIdentityKeyPath(omemoManager, device); 139 try { 140 byte[] bytes = readBytes(identityKeyPath); 141 return bytes != null ? keyUtil().identityKeyFromBytes(bytes) : null; 142 } catch (IOException e) { 143 return null; 144 } 145 } 146 147 @Override 148 public void storeOmemoIdentityKey(OmemoManager omemoManager, OmemoDevice device, T_IdKey t_idKey) { 149 File identityKeyPath = hierarchy.getContactsIdentityKeyPath(omemoManager, device); 150 try { 151 writeBytes(identityKeyPath, keyUtil().identityKeyToBytes(t_idKey)); 152 } catch (IOException e) { 153 LOGGER.log(Level.SEVERE, "Could not write omemoIdentityKey of " + device + ": " + e, e); 154 } 155 } 156 157 @Override 158 public boolean isTrustedOmemoIdentity(OmemoManager omemoManager, OmemoDevice device, OmemoFingerprint fingerprint) { 159 File trustPath = hierarchy.getContactsTrustPath(omemoManager, device); 160 try { 161 String depositedFingerprint = new String(readBytes(trustPath), StringUtils.UTF8); 162 163 return depositedFingerprint.length() > 2 164 && depositedFingerprint.charAt(0) == '1' 165 && new OmemoFingerprint(depositedFingerprint.substring(2)).equals(fingerprint); 166 } catch (IOException e) { 167 return false; 168 } 169 } 170 171 @Override 172 public boolean isDecidedOmemoIdentity(OmemoManager omemoManager, OmemoDevice device, OmemoFingerprint fingerprint) { 173 File trustPath = hierarchy.getContactsTrustPath(omemoManager, device); 174 try { 175 String depositedFingerprint = new String(readBytes(trustPath), StringUtils.UTF8); 176 177 return depositedFingerprint.length() > 2 178 && (depositedFingerprint.charAt(0) == '1' || depositedFingerprint.charAt(0) == '2') 179 && new OmemoFingerprint(depositedFingerprint.substring(2)).equals(fingerprint); 180 } catch (IOException e) { 181 return false; 182 } 183 } 184 185 @Override 186 public void trustOmemoIdentity(OmemoManager omemoManager, OmemoDevice device, OmemoFingerprint fingerprint) { 187 File trustPath = hierarchy.getContactsTrustPath(omemoManager, device); 188 try { 189 writeBytes(trustPath, ("1 " + fingerprint.toString()).getBytes(StringUtils.UTF8)); 190 } catch (IOException e) { 191 LOGGER.log(Level.SEVERE, "Could not trust " + device + ": " + e, e); 192 } 193 } 194 195 @Override 196 public void distrustOmemoIdentity(OmemoManager omemoManager, OmemoDevice device, OmemoFingerprint fingerprint) { 197 File trustPath = hierarchy.getContactsTrustPath(omemoManager, device); 198 try { 199 writeBytes(trustPath, ("2 " + fingerprint.toString()).getBytes(StringUtils.UTF8)); 200 } catch (IOException e) { 201 LOGGER.log(Level.SEVERE, "Could not distrust " + device + ": " + e, e); 202 } 203 } 204 205 @Override 206 public void setDateOfLastReceivedMessage(OmemoManager omemoManager, OmemoDevice from, Date date) { 207 File lastMessageReceived = hierarchy.getLastMessageReceivedDatePath(omemoManager, from); 208 try { 209 writeLong(lastMessageReceived, date.getTime()); 210 } catch (IOException e) { 211 LOGGER.log(Level.SEVERE, "Could not write date of last received message from " + from + ": " + e, e); 212 } 213 } 214 215 @Override 216 public Date getDateOfLastReceivedMessage(OmemoManager omemoManager, OmemoDevice from) { 217 File lastMessageReceived = hierarchy.getLastMessageReceivedDatePath(omemoManager, from); 218 try { 219 long date = readLong(lastMessageReceived); 220 return date != -1 ? new Date(date) : null; 221 } catch (IOException e) { 222 return null; 223 } 224 } 225 226 @Override 227 public void setDateOfLastSignedPreKeyRenewal(OmemoManager omemoManager, Date date) { 228 File lastSignedPreKeyRenewal = hierarchy.getLastSignedPreKeyRenewal(omemoManager); 229 try { 230 writeLong(lastSignedPreKeyRenewal, date.getTime()); 231 } catch (IOException e) { 232 LOGGER.log(Level.SEVERE, "Could not write date of last singedPreKey renewal for " 233 + omemoManager.getOwnDevice() + ": " + e, e); 234 } 235 } 236 237 @Override 238 public Date getDateOfLastSignedPreKeyRenewal(OmemoManager omemoManager) { 239 File lastSignedPreKeyRenewal = hierarchy.getLastSignedPreKeyRenewal(omemoManager); 240 241 try { 242 long date = readLong(lastSignedPreKeyRenewal); 243 return date != -1 ? new Date(date) : null; 244 } catch (IOException e) { 245 return null; 246 } 247 } 248 249 @Override 250 public T_PreKey loadOmemoPreKey(OmemoManager omemoManager, int preKeyId) { 251 File preKeyPath = hierarchy.getPreKeyPath(omemoManager, preKeyId); 252 try { 253 byte[] bytes = readBytes(preKeyPath); 254 return bytes != null ? keyUtil().preKeyFromBytes(bytes) : null; 255 } catch (IOException e) { 256 return null; 257 } 258 } 259 260 @Override 261 public void storeOmemoPreKey(OmemoManager omemoManager, int preKeyId, T_PreKey t_preKey) { 262 File preKeyPath = hierarchy.getPreKeyPath(omemoManager, preKeyId); 263 try { 264 writeBytes(preKeyPath, keyUtil().preKeyToBytes(t_preKey)); 265 } catch (IOException e) { 266 LOGGER.log(Level.SEVERE, "Could not write preKey with id " + preKeyId + ": " + e, e); 267 } 268 } 269 270 @Override 271 public void removeOmemoPreKey(OmemoManager omemoManager, int preKeyId) { 272 File preKeyPath = hierarchy.getPreKeyPath(omemoManager, preKeyId); 273 preKeyPath.delete(); 274 } 275 276 @Override 277 public int loadCurrentSignedPreKeyId(OmemoManager omemoManager) { 278 File currentSignedPreKeyIdPath = hierarchy.getCurrentSignedPreKeyIdPath(omemoManager); 279 try { 280 int i = readInt(currentSignedPreKeyIdPath); 281 return i == -1 ? 0 : i; 282 } catch (IOException e) { 283 return 0; 284 } 285 } 286 287 @Override 288 public void storeCurrentSignedPreKeyId(OmemoManager omemoManager, int currentSignedPreKeyId) { 289 File currentSignedPreKeyIdPath = hierarchy.getCurrentSignedPreKeyIdPath(omemoManager); 290 try { 291 writeInt(currentSignedPreKeyIdPath, currentSignedPreKeyId); 292 } catch (IOException e) { 293 LOGGER.log(Level.SEVERE, "Could not write currentSignedPreKeyId " 294 + currentSignedPreKeyId + " for " + omemoManager.getOwnDevice() + ": " 295 + e, e); 296 } 297 } 298 299 @Override 300 public HashMap<Integer, T_PreKey> loadOmemoPreKeys(OmemoManager omemoManager) { 301 File preKeyDirectory = hierarchy.getPreKeysDirectory(omemoManager); 302 HashMap<Integer, T_PreKey> preKeys = new HashMap<>(); 303 304 if (preKeyDirectory == null) { 305 return preKeys; 306 } 307 308 File[] keys = preKeyDirectory.listFiles(); 309 for (File f : keys != null ? keys : new File[0]) { 310 311 try { 312 byte[] bytes = readBytes(f); 313 if (bytes == null) { 314 continue; 315 } 316 T_PreKey p = keyUtil().preKeyFromBytes(bytes); 317 preKeys.put(Integer.parseInt(f.getName()), p); 318 319 } catch (IOException e) { 320 // Do nothing. 321 } 322 } 323 return preKeys; 324 } 325 326 @Override 327 public T_SigPreKey loadOmemoSignedPreKey(OmemoManager omemoManager, int signedPreKeyId) { 328 File signedPreKeyPath = new File(hierarchy.getSignedPreKeysDirectory(omemoManager), Integer.toString(signedPreKeyId)); 329 try { 330 byte[] bytes = readBytes(signedPreKeyPath); 331 return bytes != null ? keyUtil().signedPreKeyFromBytes(bytes) : null; 332 } catch (IOException e) { 333 return null; 334 } 335 } 336 337 @Override 338 public HashMap<Integer, T_SigPreKey> loadOmemoSignedPreKeys(OmemoManager omemoManager) { 339 File signedPreKeysDirectory = hierarchy.getSignedPreKeysDirectory(omemoManager); 340 HashMap<Integer, T_SigPreKey> signedPreKeys = new HashMap<>(); 341 342 if (signedPreKeysDirectory == null) { 343 return signedPreKeys; 344 } 345 346 File[] keys = signedPreKeysDirectory.listFiles(); 347 for (File f : keys != null ? keys : new File[0]) { 348 349 try { 350 byte[] bytes = readBytes(f); 351 if (bytes == null) { 352 continue; 353 } 354 T_SigPreKey p = keyUtil().signedPreKeyFromBytes(bytes); 355 signedPreKeys.put(Integer.parseInt(f.getName()), p); 356 357 } catch (IOException e) { 358 // Do nothing. 359 } 360 } 361 return signedPreKeys; 362 } 363 364 @Override 365 public void storeOmemoSignedPreKey(OmemoManager omemoManager, int signedPreKeyId, T_SigPreKey signedPreKey) { 366 File signedPreKeyPath = new File(hierarchy.getSignedPreKeysDirectory(omemoManager), Integer.toString(signedPreKeyId)); 367 try { 368 writeBytes(signedPreKeyPath, keyUtil().signedPreKeyToBytes(signedPreKey)); 369 } catch (IOException e) { 370 LOGGER.log(Level.SEVERE, "Could not write signedPreKey " + signedPreKey 371 + " for " + omemoManager.getOwnDevice() + ": " + e, e); 372 } 373 } 374 375 @Override 376 public void removeOmemoSignedPreKey(OmemoManager omemoManager, int signedPreKeyId) { 377 File signedPreKeyPath = new File(hierarchy.getSignedPreKeysDirectory(omemoManager), Integer.toString(signedPreKeyId)); 378 signedPreKeyPath.delete(); 379 } 380 381 @Override 382 public T_Sess loadRawSession(OmemoManager omemoManager, OmemoDevice device) { 383 File sessionPath = hierarchy.getContactsSessionPath(omemoManager, device); 384 try { 385 byte[] bytes = readBytes(sessionPath); 386 return bytes != null ? keyUtil().rawSessionFromBytes(bytes) : null; 387 } catch (IOException e) { 388 return null; 389 } 390 } 391 392 @Override 393 public HashMap<Integer, T_Sess> loadAllRawSessionsOf(OmemoManager omemoManager, BareJid contact) { 394 File contactsDirectory = hierarchy.getContactsDir(omemoManager, contact); 395 HashMap<Integer, T_Sess> sessions = new HashMap<>(); 396 String[] devices = contactsDirectory.list(); 397 398 for (String deviceId : devices != null ? devices : new String[0]) { 399 int id; 400 try { 401 id = Integer.parseInt(deviceId); 402 } catch (NumberFormatException e) { 403 continue; 404 } 405 OmemoDevice device = new OmemoDevice(contact, id); 406 File session = hierarchy.getContactsSessionPath(omemoManager, device); 407 408 try { 409 byte[] bytes = readBytes(session); 410 if (bytes == null) { 411 continue; 412 } 413 T_Sess s = keyUtil().rawSessionFromBytes(bytes); 414 sessions.put(id, s); 415 416 } catch (IOException e) { 417 // Do nothing. 418 } 419 } 420 return sessions; 421 } 422 423 @Override 424 public void storeRawSession(OmemoManager omemoManager, OmemoDevice device, T_Sess session) { 425 File sessionPath = hierarchy.getContactsSessionPath(omemoManager, device); 426 try { 427 writeBytes(sessionPath, keyUtil().rawSessionToBytes(session)); 428 } catch (IOException e) { 429 LOGGER.log(Level.SEVERE, "Could not write session between our device " + omemoManager.getOwnDevice() 430 + " and their device " + device + ": " + e.getMessage()); 431 } 432 } 433 434 @Override 435 public void removeRawSession(OmemoManager omemoManager, OmemoDevice device) { 436 File sessionPath = hierarchy.getContactsSessionPath(omemoManager, device); 437 sessionPath.delete(); 438 } 439 440 @Override 441 public void removeAllRawSessionsOf(OmemoManager omemoManager, BareJid contact) { 442 File contactsDirectory = hierarchy.getContactsDir(omemoManager, contact); 443 String[] devices = contactsDirectory.list(); 444 445 for (String deviceId : devices != null ? devices : new String[0]) { 446 int id; 447 try { 448 id = Integer.parseInt(deviceId); 449 } catch (NumberFormatException e) { 450 continue; 451 } 452 OmemoDevice device = new OmemoDevice(contact, id); 453 File session = hierarchy.getContactsSessionPath(omemoManager, device); 454 session.delete(); 455 } 456 } 457 458 @Override 459 public boolean containsRawSession(OmemoManager omemoManager, OmemoDevice device) { 460 File session = hierarchy.getContactsSessionPath(omemoManager, device); 461 return session.exists(); 462 } 463 464 @Override 465 public CachedDeviceList loadCachedDeviceList(OmemoManager omemoManager, BareJid contact) { 466 CachedDeviceList cachedDeviceList = new CachedDeviceList(); 467 468 if (contact == null) { 469 return null; 470 } 471 472 // active 473 File activeDevicesPath = hierarchy.getContactsActiveDevicesPath(omemoManager, contact); 474 try { 475 cachedDeviceList.getActiveDevices().addAll(readIntegers(activeDevicesPath)); 476 } catch (IOException e) { 477 // Don't worry... 478 } 479 480 // inactive 481 File inactiveDevicesPath = hierarchy.getContactsInactiveDevicesPath(omemoManager, contact); 482 try { 483 cachedDeviceList.getInactiveDevices().addAll(readIntegers(inactiveDevicesPath)); 484 } catch (IOException e) { 485 // It's ok :) 486 } 487 488 return cachedDeviceList; 489 } 490 491 @Override 492 public void storeCachedDeviceList(OmemoManager omemoManager, BareJid contact, CachedDeviceList deviceList) { 493 if (contact == null) { 494 return; 495 } 496 497 File activeDevices = hierarchy.getContactsActiveDevicesPath(omemoManager, contact); 498 try { 499 writeIntegers(activeDevices, deviceList.getActiveDevices()); 500 } catch (IOException e) { 501 LOGGER.log(Level.SEVERE, "Could not write active devices of deviceList of " 502 + contact + ": " + e.getMessage()); 503 } 504 505 File inactiveDevices = hierarchy.getContactsInactiveDevicesPath(omemoManager, contact); 506 try { 507 writeIntegers(inactiveDevices, deviceList.getInactiveDevices()); 508 } catch (IOException e) { 509 LOGGER.log(Level.SEVERE, "Could not write inactive devices of deviceList of " 510 + contact + ": " + e.getMessage()); 511 } 512 } 513 514 @Override 515 public void purgeOwnDeviceKeys(OmemoManager omemoManager) { 516 File deviceDirectory = hierarchy.getUserDeviceDirectory(omemoManager); 517 deleteDirectory(deviceDirectory); 518 } 519 520 private void writeInt(File target, int i) throws IOException { 521 if (target == null) { 522 throw new IOException("Could not write integer to null-path."); 523 } 524 525 FileHierarchy.createFile(target); 526 527 IOException io = null; 528 DataOutputStream out = null; 529 try { 530 out = new DataOutputStream(new FileOutputStream(target)); 531 out.writeInt(i); 532 } catch (IOException e) { 533 io = e; 534 } finally { 535 if (out != null) { 536 out.close(); 537 } 538 } 539 540 if (io != null) { 541 throw io; 542 } 543 } 544 545 private int readInt(File target) throws IOException { 546 if (target == null) { 547 throw new IOException("Could not read integer from null-path."); 548 } 549 550 IOException io = null; 551 int i = -1; 552 DataInputStream in = null; 553 554 try { 555 in = new DataInputStream(new FileInputStream(target)); 556 i = in.readInt(); 557 558 } catch (IOException e) { 559 io = e; 560 561 } finally { 562 if (in != null) { 563 in.close(); 564 } 565 } 566 567 if (io != null) { 568 throw io; 569 } 570 return i; 571 } 572 573 private void writeLong(File target, long i) throws IOException { 574 if (target == null) { 575 throw new IOException("Could not write long to null-path."); 576 } 577 578 FileHierarchy.createFile(target); 579 580 IOException io = null; 581 DataOutputStream out = null; 582 try { 583 out = new DataOutputStream(new FileOutputStream(target)); 584 out.writeLong(i); 585 586 } catch (IOException e) { 587 io = e; 588 589 } finally { 590 if (out != null) { 591 out.close(); 592 } 593 } 594 595 if (io != null) { 596 throw io; 597 } 598 } 599 600 private long readLong(File target) throws IOException { 601 if (target == null) { 602 throw new IOException("Could not read long from null-path."); 603 } 604 605 IOException io = null; 606 long l = -1; 607 DataInputStream in = null; 608 609 try { 610 in = new DataInputStream(new FileInputStream(target)); 611 l = in.readLong(); 612 613 } catch (IOException e) { 614 io = e; 615 616 } finally { 617 if (in != null) { 618 in.close(); 619 } 620 } 621 622 if (io != null) { 623 throw io; 624 } 625 626 return l; 627 } 628 629 private void writeBytes(File target, byte[] bytes) throws IOException { 630 if (target == null) { 631 throw new IOException("Could not write bytes to null-path."); 632 } 633 634 // Create file 635 FileHierarchy.createFile(target); 636 637 IOException io = null; 638 DataOutputStream out = null; 639 640 try { 641 out = new DataOutputStream(new FileOutputStream(target)); 642 out.write(bytes); 643 644 } catch (IOException e) { 645 io = e; 646 647 } finally { 648 if (out != null) { 649 out.close(); 650 } 651 } 652 653 if (io != null) { 654 throw io; 655 } 656 } 657 658 private byte[] readBytes(File target) throws IOException { 659 if (target == null) { 660 throw new IOException("Could not read bytes from null-path."); 661 } 662 663 byte[] b = null; 664 IOException io = null; 665 DataInputStream in = null; 666 667 try { 668 in = new DataInputStream(new FileInputStream(target)); 669 b = new byte[in.available()]; 670 in.read(b); 671 672 } catch (IOException e) { 673 io = e; 674 675 } finally { 676 if (in != null) { 677 in.close(); 678 } 679 } 680 681 if (io != null) { 682 throw io; 683 } 684 685 return b; 686 } 687 688 private void writeIntegers(File target, Set<Integer> integers) throws IOException { 689 if (target == null) { 690 throw new IOException("Could not write integers to null-path."); 691 } 692 693 IOException io = null; 694 DataOutputStream out = null; 695 696 try { 697 out = new DataOutputStream(new FileOutputStream(target)); 698 for (int i : integers) { 699 out.writeInt(i); 700 } 701 702 } catch (IOException e) { 703 io = e; 704 705 } finally { 706 if (out != null) { 707 out.close(); 708 } 709 } 710 711 if (io != null) { 712 throw io; 713 } 714 } 715 716 private Set<Integer> readIntegers(File target) throws IOException { 717 if (target == null) { 718 throw new IOException("Could not write integers to null-path."); 719 } 720 721 HashSet<Integer> integers = new HashSet<>(); 722 IOException io = null; 723 DataInputStream in = null; 724 725 try { 726 in = new DataInputStream(new FileInputStream(target)); 727 728 try { 729 while (true) { 730 integers.add(in.readInt()); 731 } 732 } catch (EOFException e) { 733 // Reached end of the list. 734 } 735 736 } catch (IOException e) { 737 io = e; 738 739 } finally { 740 if (in != null) { 741 in.close(); 742 } 743 } 744 745 if (io != null) { 746 throw io; 747 } 748 749 return integers; 750 } 751 752 public static void deleteDirectory(File root) { 753 File[] currList; 754 Stack<File> stack = new Stack<>(); 755 stack.push(root); 756 while (!stack.isEmpty()) { 757 if (stack.lastElement().isDirectory()) { 758 currList = stack.lastElement().listFiles(); 759 if (currList != null && currList.length > 0) { 760 for (File curr : currList) { 761 stack.push(curr); 762 } 763 } else { 764 stack.pop().delete(); 765 } 766 } else { 767 stack.pop().delete(); 768 } 769 } 770 } 771 772 /** 773 * This class represents the directory structure of the FileBasedOmemoStoreV2. 774 * The directory looks as follows: 775 * 776 * OMEMO_Store/ 777 * 'romeo@montague.lit'/ //Our bareJid 778 * ... 779 * 'juliet@capulet.lit'/ //Our other bareJid 780 * defaultDeviceId 781 * '13371234'/ //deviceId 782 * identityKeyPair //Our identityKeyPair 783 * lastPreKeyId //Id of the last preKey we generated 784 * currentSignedPreKeyId //Id of the currently used signedPreKey 785 * lastSignedPreKeyRenewal //Date of when the signedPreKey was last renewed. 786 * preKeys/ //Our preKeys 787 * '1' 788 * '2' 789 * ... 790 * signedPreKeys/ //Our signedPreKeys 791 * '1' 792 * '2' 793 * ... 794 * contacts/ 795 * 'romeo@capulet.lit'/ //Juliets contact Romeo 796 * activeDevice //List of Romeos active devices 797 * inactiveDevices //List of his inactive devices 798 * 'deviceId'/ //Romeos deviceId 799 * identityKey //Romeos identityKey 800 * session //Our session with romeo 801 * trust //Records about the trust in romeos device 802 * (lastReceivedMessageDate) //Only, for our own other devices: 803 * //date of the last received message 804 * 805 */ 806 public static class FileHierarchy { 807 808 static final String STORE = "OMEMO_Store"; 809 static final String CONTACTS = "contacts"; 810 static final String DEFAULT_DEVICE_ID = "defaultDeviceId"; 811 static final String IDENTITY_KEY = "identityKey"; 812 static final String IDENTITY_KEY_PAIR = "identityKeyPair"; 813 static final String PRE_KEYS = "preKeys"; 814 static final String LAST_MESSAGE_RECEVIED_DATE = "lastMessageReceivedDate"; 815 static final String LAST_PRE_KEY_ID = "lastPreKeyId"; 816 static final String SIGNED_PRE_KEYS = "signedPreKeys"; 817 static final String CURRENT_SIGNED_PRE_KEY_ID = "currentSignedPreKeyId"; 818 static final String LAST_SIGNED_PRE_KEY_RENEWAL = "lastSignedPreKeyRenewal"; 819 static final String SESSION = "session"; 820 static final String DEVICE_LIST_ACTIVE = "activeDevices"; 821 static final String DEVICE_LIST_INAVTIVE = "inactiveDevices"; 822 static final String TRUST = "trust"; 823 824 File basePath; 825 826 FileHierarchy(File basePath) { 827 this.basePath = basePath; 828 basePath.mkdirs(); 829 } 830 831 File getStoreDirectory() { 832 return createDirectory(basePath, STORE); 833 } 834 835 File getUserDirectory(BareJid bareJid) { 836 return createDirectory(getStoreDirectory(), bareJid.toString()); 837 } 838 839 File getUserDeviceDirectory(OmemoManager omemoManager) { 840 return createDirectory(getUserDirectory(omemoManager.getOwnJid()), 841 Integer.toString(omemoManager.getDeviceId())); 842 } 843 844 File getContactsDir(OmemoManager omemoManager) { 845 return createDirectory(getUserDeviceDirectory(omemoManager), CONTACTS); 846 } 847 848 File getContactsDir(OmemoManager omemoManager, BareJid contact) { 849 return createDirectory(getContactsDir(omemoManager), contact.toString()); 850 } 851 852 File getContactsDir(OmemoManager omemoManager, OmemoDevice omemoDevice) { 853 return createDirectory(getContactsDir(omemoManager, omemoDevice.getJid()), 854 Integer.toString(omemoDevice.getDeviceId())); 855 } 856 857 File getIdentityKeyPairPath(OmemoManager omemoManager) { 858 return new File(getUserDeviceDirectory(omemoManager), IDENTITY_KEY_PAIR); 859 } 860 861 File getPreKeysDirectory(OmemoManager omemoManager) { 862 return createDirectory(getUserDeviceDirectory(omemoManager), PRE_KEYS); 863 } 864 865 File getPreKeyPath(OmemoManager omemoManager, int preKeyId) { 866 return new File(getPreKeysDirectory(omemoManager), Integer.toString(preKeyId)); 867 } 868 869 File getLastMessageReceivedDatePath(OmemoManager omemoManager, OmemoDevice device) { 870 return new File(getContactsDir(omemoManager, device), LAST_MESSAGE_RECEVIED_DATE); 871 } 872 873 File getLastPreKeyIdPath(OmemoManager omemoManager) { 874 return new File(getUserDeviceDirectory(omemoManager), LAST_PRE_KEY_ID); 875 } 876 877 File getSignedPreKeysDirectory(OmemoManager omemoManager) { 878 return createDirectory(getUserDeviceDirectory(omemoManager), SIGNED_PRE_KEYS); 879 } 880 881 File getCurrentSignedPreKeyIdPath(OmemoManager omemoManager) { 882 return new File(getUserDeviceDirectory(omemoManager), CURRENT_SIGNED_PRE_KEY_ID); 883 } 884 885 File getLastSignedPreKeyRenewal(OmemoManager omemoManager) { 886 return new File(getUserDeviceDirectory(omemoManager), LAST_SIGNED_PRE_KEY_RENEWAL); 887 } 888 889 File getDefaultDeviceIdPath(BareJid bareJid) { 890 return new File(getUserDirectory(bareJid), DEFAULT_DEVICE_ID); 891 } 892 893 File getContactsIdentityKeyPath(OmemoManager omemoManager, OmemoDevice omemoDevice) { 894 return new File(getContactsDir(omemoManager, omemoDevice), IDENTITY_KEY); 895 896 } 897 898 File getContactsSessionPath(OmemoManager omemoManager, OmemoDevice omemoDevice) { 899 return new File(getContactsDir(omemoManager, omemoDevice), SESSION); 900 } 901 902 File getContactsActiveDevicesPath(OmemoManager omemoManager, BareJid contact) { 903 return new File(getContactsDir(omemoManager, contact), DEVICE_LIST_ACTIVE); 904 } 905 906 File getContactsInactiveDevicesPath(OmemoManager omemoManager, BareJid contact) { 907 return new File(getContactsDir(omemoManager, contact), DEVICE_LIST_INAVTIVE); 908 } 909 910 File getContactsTrustPath(OmemoManager omemoManager, OmemoDevice omemoDevice) { 911 return new File(getContactsDir(omemoManager, omemoDevice), TRUST); 912 913 } 914 915 private static File createFile(File f) throws IOException { 916 File p = f.getParentFile(); 917 createDirectory(p); 918 f.createNewFile(); 919 return f; 920 921 } 922 923 private static File createFile(File dir, String filename) throws IOException { 924 return createFile(new File(dir, filename)); 925 } 926 927 private static File createDirectory(File dir, String subdir) { 928 File f = new File(dir, subdir); 929 return createDirectory(f); 930 } 931 932 private static File createDirectory(File f) { 933 if (f.exists() && f.isDirectory()) { 934 return f; 935 } 936 937 f.mkdirs(); 938 return f; 939 } 940 } 941}