FileBasedOmemoStore.java

  1. /**
  2.  *
  3.  * Copyright 2017 Paul Schaub, 2019 Florian Schmaus.
  4.  *
  5.  * Licensed under the Apache License, Version 2.0 (the "License");
  6.  * you may not use this file except in compliance with the License.
  7.  * You may obtain a copy of the License at
  8.  *
  9.  *     http://www.apache.org/licenses/LICENSE-2.0
  10.  *
  11.  * Unless required by applicable law or agreed to in writing, software
  12.  * distributed under the License is distributed on an "AS IS" BASIS,
  13.  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  14.  * See the License for the specific language governing permissions and
  15.  * limitations under the License.
  16.  */
  17. package org.jivesoftware.smackx.omemo;

  18. import java.io.DataInputStream;
  19. import java.io.DataOutputStream;
  20. import java.io.EOFException;
  21. import java.io.File;
  22. import java.io.FileInputStream;
  23. import java.io.FileOutputStream;
  24. import java.io.IOException;
  25. import java.util.Collections;
  26. import java.util.Date;
  27. import java.util.HashMap;
  28. import java.util.HashSet;
  29. import java.util.Set;
  30. import java.util.SortedSet;
  31. import java.util.Stack;
  32. import java.util.TreeMap;
  33. import java.util.TreeSet;
  34. import java.util.logging.Level;
  35. import java.util.logging.Logger;

  36. import org.jivesoftware.smack.util.stringencoder.BareJidEncoder;

  37. import org.jivesoftware.smackx.omemo.exceptions.CorruptedOmemoKeyException;
  38. import org.jivesoftware.smackx.omemo.internal.OmemoCachedDeviceList;
  39. import org.jivesoftware.smackx.omemo.internal.OmemoDevice;

  40. import org.jxmpp.jid.BareJid;

  41. /**
  42.  * Like a rocket!
  43.  * Implementation of the {@link OmemoStore} class that uses plain files for storage.
  44.  *
  45.  * @author Paul Schaub
  46.  */
  47. public abstract class FileBasedOmemoStore<T_IdKeyPair, T_IdKey, T_PreKey, T_SigPreKey, T_Sess, T_Addr, T_ECPub, T_Bundle, T_Ciph>
  48.         extends OmemoStore<T_IdKeyPair, T_IdKey, T_PreKey, T_SigPreKey, T_Sess, T_Addr, T_ECPub, T_Bundle, T_Ciph> {

  49.     private final FileHierarchy hierarchy;
  50.     private static final Logger LOGGER = Logger.getLogger(FileBasedOmemoStore.class.getName());
  51.     private static BareJidEncoder bareJidEncoder = new BareJidEncoder.UrlSafeEncoder();

  52.     public FileBasedOmemoStore(File basePath) {
  53.         super();
  54.         if (basePath == null) {
  55.             throw new IllegalStateException("No FileBasedOmemoStoreDefaultPath set in OmemoConfiguration.");
  56.         }
  57.         this.hierarchy = new FileHierarchy(basePath);
  58.     }

  59.     @Override
  60.     public T_IdKeyPair loadOmemoIdentityKeyPair(OmemoDevice userDevice)
  61.             throws CorruptedOmemoKeyException, IOException {
  62.         File identityKeyPairPath = hierarchy.getIdentityKeyPairPath(userDevice);
  63.         return keyUtil().identityKeyPairFromBytes(readBytes(identityKeyPairPath));
  64.     }

  65.     @Override
  66.     public void storeOmemoIdentityKeyPair(OmemoDevice userDevice, T_IdKeyPair identityKeyPair) throws IOException {
  67.         File identityKeyPairPath = hierarchy.getIdentityKeyPairPath(userDevice);
  68.         writeBytes(identityKeyPairPath, keyUtil().identityKeyPairToBytes(identityKeyPair));
  69.     }

  70.     @Override
  71.     public void removeOmemoIdentityKeyPair(OmemoDevice userDevice) {
  72.         File identityKeyPairPath = hierarchy.getIdentityKeyPairPath(userDevice);
  73.         if (!identityKeyPairPath.delete()) {
  74.             LOGGER.log(Level.WARNING, "Could not delete OMEMO IdentityKeyPair " + identityKeyPairPath.getAbsolutePath());
  75.         }
  76.     }

  77.     @Override
  78.     public T_IdKey loadOmemoIdentityKey(OmemoDevice userDevice, OmemoDevice contactsDevice)
  79.             throws CorruptedOmemoKeyException, IOException {
  80.         File identityKeyPath = hierarchy.getContactsIdentityKeyPath(userDevice, contactsDevice);
  81.         byte[] bytes = readBytes(identityKeyPath);
  82.         return bytes != null ? keyUtil().identityKeyFromBytes(bytes) : null;
  83.     }

  84.     @Override
  85.     public void storeOmemoIdentityKey(OmemoDevice userDevice, OmemoDevice contactsDevice, T_IdKey t_idKey) throws IOException {
  86.         File identityKeyPath = hierarchy.getContactsIdentityKeyPath(userDevice, contactsDevice);
  87.         writeBytes(identityKeyPath, keyUtil().identityKeyToBytes(t_idKey));
  88.     }

  89.     @Override
  90.     public void removeOmemoIdentityKey(OmemoDevice userDevice, OmemoDevice contactsDevice) {
  91.         File identityKeyPath = hierarchy.getContactsIdentityKeyPath(userDevice, contactsDevice);
  92.         if (!identityKeyPath.delete()) {
  93.             LOGGER.log(Level.WARNING, "Could not delete OMEMO identityKey " + identityKeyPath.getAbsolutePath());
  94.         }
  95.     }

  96.     @Override
  97.     public SortedSet<Integer> localDeviceIdsOf(BareJid localUser) {
  98.         SortedSet<Integer> deviceIds = new TreeSet<>();
  99.         File userDir = hierarchy.getUserDirectory(localUser);
  100.         File[] list = userDir.listFiles();
  101.         for (File d : list != null ? list : new File[] {}) {
  102.             if (d.isDirectory()) {
  103.                 try {
  104.                     deviceIds.add(Integer.parseInt(d.getName()));
  105.                 } catch (NumberFormatException e) {
  106.                     // ignore
  107.                 }
  108.             }
  109.         }
  110.         return deviceIds;
  111.     }

  112.     @Override
  113.     public void setDateOfLastReceivedMessage(OmemoDevice userDevice, OmemoDevice contactsDevice, Date date) throws IOException {
  114.         File lastMessageReceived = hierarchy.getLastMessageReceivedDatePath(userDevice, contactsDevice);
  115.         writeLong(lastMessageReceived, date.getTime());
  116.     }

  117.     @Override
  118.     public Date getDateOfLastReceivedMessage(OmemoDevice userDevice, OmemoDevice contactsDevice) throws IOException {
  119.         File lastMessageReceived = hierarchy.getLastMessageReceivedDatePath(userDevice, contactsDevice);
  120.         Long date = readLong(lastMessageReceived);
  121.         return date != null ? new Date(date) : null;
  122.     }

  123.     @Override
  124.     public void setDateOfLastDeviceIdPublication(OmemoDevice userDevice, OmemoDevice contactsDevice, Date date) throws IOException {
  125.         File lastDeviceIdPublished = hierarchy.getLastDeviceIdPublicationDatePath(userDevice, contactsDevice);
  126.         writeLong(lastDeviceIdPublished, date.getTime());
  127.     }

  128.     @Override
  129.     public Date getDateOfLastDeviceIdPublication(OmemoDevice userDevice, OmemoDevice contactsDevice) throws IOException {
  130.         File lastDeviceIdPublished = hierarchy.getLastDeviceIdPublicationDatePath(userDevice, contactsDevice);
  131.         Long date = readLong(lastDeviceIdPublished);
  132.         return date != null ? new Date(date) : null;
  133.     }

  134.     @Override
  135.     public void setDateOfLastSignedPreKeyRenewal(OmemoDevice userDevice, Date date) throws IOException {
  136.         File lastSignedPreKeyRenewal = hierarchy.getLastSignedPreKeyRenewal(userDevice);
  137.         writeLong(lastSignedPreKeyRenewal, date.getTime());
  138.     }

  139.     @Override
  140.     public Date getDateOfLastSignedPreKeyRenewal(OmemoDevice userDevice) throws IOException {
  141.         File lastSignedPreKeyRenewal = hierarchy.getLastSignedPreKeyRenewal(userDevice);
  142.         Long date = readLong(lastSignedPreKeyRenewal);
  143.         return date != null ? new Date(date) : null;
  144.     }

  145.     @Override
  146.     public T_PreKey loadOmemoPreKey(OmemoDevice userDevice, int preKeyId) throws IOException {
  147.         File preKeyPath = hierarchy.getPreKeyPath(userDevice, preKeyId);
  148.         byte[] bytes = readBytes(preKeyPath);

  149.         if (bytes != null) {
  150.             try {
  151.                 return keyUtil().preKeyFromBytes(bytes);
  152.             } catch (IOException e) {
  153.                 LOGGER.log(Level.WARNING, "Could not deserialize preKey from bytes.", e);
  154.             }
  155.         }

  156.         return null;
  157.     }

  158.     @Override
  159.     public void storeOmemoPreKey(OmemoDevice userDevice, int preKeyId, T_PreKey t_preKey) throws IOException {
  160.         File preKeyPath = hierarchy.getPreKeyPath(userDevice, preKeyId);
  161.         writeBytes(preKeyPath, keyUtil().preKeyToBytes(t_preKey));
  162.     }

  163.     @Override
  164.     public void removeOmemoPreKey(OmemoDevice userDevice, int preKeyId) {
  165.         File preKeyPath = hierarchy.getPreKeyPath(userDevice, preKeyId);
  166.         if (!preKeyPath.delete()) {
  167.             LOGGER.log(Level.WARNING, "Deleting OMEMO preKey " + preKeyPath.getAbsolutePath() + " failed.");
  168.         }
  169.     }

  170.     @Override
  171.     public TreeMap<Integer, T_PreKey> loadOmemoPreKeys(OmemoDevice userDevice) throws IOException {
  172.         File preKeyDirectory = hierarchy.getPreKeysDirectory(userDevice);
  173.         TreeMap<Integer, T_PreKey> preKeys = new TreeMap<>();

  174.         if (preKeyDirectory == null) {
  175.             return preKeys;
  176.         }

  177.         File[] keys = preKeyDirectory.listFiles();

  178.         for (File f : keys != null ? keys : new File[0]) {
  179.             byte[] bytes = readBytes(f);
  180.             if (bytes != null) {
  181.                 try {
  182.                     T_PreKey p = keyUtil().preKeyFromBytes(bytes);
  183.                     preKeys.put(Integer.parseInt(f.getName()), p);
  184.                 } catch (IOException e) {
  185.                     LOGGER.log(Level.WARNING, "Could not deserialize preKey from bytes.", e);
  186.                 }
  187.             }
  188.         }

  189.         return preKeys;
  190.     }

  191.     @Override
  192.     public T_SigPreKey loadOmemoSignedPreKey(OmemoDevice userDevice, int signedPreKeyId) throws IOException {
  193.         File signedPreKeyPath = new File(hierarchy.getSignedPreKeysDirectory(userDevice), Integer.toString(signedPreKeyId));
  194.         byte[] bytes = readBytes(signedPreKeyPath);
  195.         if (bytes != null) {
  196.             try {
  197.                 return keyUtil().signedPreKeyFromBytes(bytes);
  198.             } catch (IOException e) {
  199.                 LOGGER.log(Level.WARNING, "Could not deserialize signed preKey from bytes.", e);
  200.             }
  201.         }
  202.         return null;
  203.     }

  204.     @Override
  205.     public TreeMap<Integer, T_SigPreKey> loadOmemoSignedPreKeys(OmemoDevice userDevice) throws IOException {
  206.         File signedPreKeysDirectory = hierarchy.getSignedPreKeysDirectory(userDevice);
  207.         TreeMap<Integer, T_SigPreKey> signedPreKeys = new TreeMap<>();

  208.         if (signedPreKeysDirectory == null) {
  209.             return signedPreKeys;
  210.         }

  211.         File[] keys = signedPreKeysDirectory.listFiles();

  212.         for (File f : keys != null ? keys : new File[0]) {
  213.             byte[] bytes = readBytes(f);
  214.             if (bytes != null) {
  215.                 try {
  216.                     T_SigPreKey p = keyUtil().signedPreKeyFromBytes(bytes);
  217.                     signedPreKeys.put(Integer.parseInt(f.getName()), p);
  218.                 } catch (IOException e) {
  219.                     LOGGER.log(Level.WARNING, "Could not deserialize signed preKey.", e);
  220.                 }
  221.             }
  222.         }

  223.         return signedPreKeys;
  224.     }

  225.     @Override
  226.     public void storeOmemoSignedPreKey(OmemoDevice userDevice,
  227.                                        int signedPreKeyId,
  228.                                        T_SigPreKey signedPreKey) throws IOException {
  229.         File signedPreKeyPath = new File(hierarchy.getSignedPreKeysDirectory(userDevice), Integer.toString(signedPreKeyId));
  230.         writeBytes(signedPreKeyPath, keyUtil().signedPreKeyToBytes(signedPreKey));
  231.     }

  232.     @Override
  233.     public void removeOmemoSignedPreKey(OmemoDevice userDevice, int signedPreKeyId) {
  234.         File signedPreKeyPath = new File(hierarchy.getSignedPreKeysDirectory(userDevice), Integer.toString(signedPreKeyId));
  235.         if (!signedPreKeyPath.delete()) {
  236.             LOGGER.log(Level.WARNING, "Deleting signed OMEMO preKey " + signedPreKeyPath.getAbsolutePath() + " failed.");
  237.         }
  238.     }

  239.     @Override
  240.     public T_Sess loadRawSession(OmemoDevice userDevice, OmemoDevice contactsDevice) throws IOException {
  241.         File sessionPath = hierarchy.getContactsSessionPath(userDevice, contactsDevice);
  242.         byte[] bytes = readBytes(sessionPath);
  243.         if (bytes != null) {
  244.             try {
  245.                 return keyUtil().rawSessionFromBytes(bytes);
  246.             } catch (IOException e) {
  247.                 LOGGER.log(Level.WARNING, "Could not deserialize raw session.", e);
  248.             }
  249.         }
  250.         return null;
  251.     }

  252.     @Override
  253.     public HashMap<Integer, T_Sess> loadAllRawSessionsOf(OmemoDevice userDevice, BareJid contact) throws IOException {
  254.         File contactsDirectory = hierarchy.getContactsDir(userDevice, contact);
  255.         HashMap<Integer, T_Sess> sessions = new HashMap<>();
  256.         String[] devices = contactsDirectory.list();

  257.         for (String deviceId : devices != null ? devices : new String[0]) {
  258.             int id;
  259.             try {
  260.                 id = Integer.parseInt(deviceId);
  261.             } catch (NumberFormatException e) {
  262.                 continue;
  263.             }
  264.             OmemoDevice device = new OmemoDevice(contact, id);
  265.             File session = hierarchy.getContactsSessionPath(userDevice, device);

  266.             byte[] bytes = readBytes(session);

  267.             if (bytes != null) {
  268.                 try {
  269.                     T_Sess s = keyUtil().rawSessionFromBytes(bytes);
  270.                     sessions.put(id, s);
  271.                 } catch (IOException e) {
  272.                     LOGGER.log(Level.WARNING, "Could not deserialize raw session.", e);
  273.                 }
  274.             }

  275.         }
  276.         return sessions;
  277.     }

  278.     @Override
  279.     public void storeRawSession(OmemoDevice userDevice, OmemoDevice contactsDevice, T_Sess session) throws IOException {
  280.         File sessionPath = hierarchy.getContactsSessionPath(userDevice, contactsDevice);
  281.         writeBytes(sessionPath, keyUtil().rawSessionToBytes(session));
  282.     }

  283.     @Override
  284.     public void removeRawSession(OmemoDevice userDevice, OmemoDevice contactsDevice) {
  285.         File sessionPath = hierarchy.getContactsSessionPath(userDevice, contactsDevice);
  286.         if (!sessionPath.delete()) {
  287.             LOGGER.log(Level.WARNING, "Deleting raw OMEMO session " + sessionPath.getAbsolutePath() + " failed.");
  288.         }
  289.     }

  290.     @Override
  291.     public void removeAllRawSessionsOf(OmemoDevice userDevice, BareJid contact) {
  292.         File contactsDirectory = hierarchy.getContactsDir(userDevice, contact);
  293.         String[] devices = contactsDirectory.list();

  294.         for (String deviceId : devices != null ? devices : new String[0]) {
  295.             int id = Integer.parseInt(deviceId);
  296.             OmemoDevice device = new OmemoDevice(contact, id);
  297.             File session = hierarchy.getContactsSessionPath(userDevice, device);
  298.             if (!session.delete()) {
  299.                 LOGGER.log(Level.WARNING, "Deleting raw OMEMO session " + session.getAbsolutePath() + "failed.");
  300.             }
  301.         }
  302.     }

  303.     @Override
  304.     public boolean containsRawSession(OmemoDevice userDevice, OmemoDevice contactsDevice) {
  305.         File session = hierarchy.getContactsSessionPath(userDevice, contactsDevice);
  306.         return session.exists();
  307.     }

  308.     @Override
  309.     public void storeOmemoMessageCounter(OmemoDevice userDevice, OmemoDevice contactsDevice, int counter) throws IOException {
  310.         File messageCounterFile = hierarchy.getDevicesMessageCounterPath(userDevice, contactsDevice);
  311.         writeIntegers(messageCounterFile, Collections.singleton(counter));
  312.     }

  313.     @Override
  314.     public int loadOmemoMessageCounter(OmemoDevice userDevice, OmemoDevice contactsDevice) throws IOException {
  315.         File messageCounterFile = hierarchy.getDevicesMessageCounterPath(userDevice, contactsDevice);
  316.         Set<Integer> integers = readIntegers(messageCounterFile);

  317.         if (integers == null || integers.isEmpty()) {
  318.             return 0;
  319.         }

  320.         return integers.iterator().next();
  321.     }

  322.     @Override
  323.     public OmemoCachedDeviceList loadCachedDeviceList(OmemoDevice userDevice, BareJid contact) throws IOException {
  324.         OmemoCachedDeviceList cachedDeviceList = new OmemoCachedDeviceList();

  325.         if (contact == null) {
  326.             throw new IllegalArgumentException("Contact can not be null.");
  327.         }

  328.         // active
  329.         File activeDevicesPath = hierarchy.getContactsActiveDevicesPath(userDevice, contact);
  330.         Set<Integer> active = readIntegers(activeDevicesPath);
  331.         if (active != null) {
  332.             cachedDeviceList.getActiveDevices().addAll(active);
  333.         }

  334.         // inactive
  335.         File inactiveDevicesPath = hierarchy.getContactsInactiveDevicesPath(userDevice, contact);
  336.         Set<Integer> inactive = readIntegers(inactiveDevicesPath);
  337.         if (inactive != null) {
  338.             cachedDeviceList.getInactiveDevices().addAll(inactive);
  339.         }

  340.         return cachedDeviceList;
  341.     }

  342.     @Override
  343.     public void storeCachedDeviceList(OmemoDevice userDevice,
  344.                                       BareJid contact,
  345.                                       OmemoCachedDeviceList contactsDeviceList) throws IOException {
  346.         if (contact == null) {
  347.             return;
  348.         }

  349.         File activeDevices = hierarchy.getContactsActiveDevicesPath(userDevice, contact);
  350.         writeIntegers(activeDevices, contactsDeviceList.getActiveDevices());

  351.         File inactiveDevices = hierarchy.getContactsInactiveDevicesPath(userDevice, contact);
  352.         writeIntegers(inactiveDevices, contactsDeviceList.getInactiveDevices());
  353.     }

  354.     @Override
  355.     public void purgeOwnDeviceKeys(OmemoDevice userDevice) {
  356.         File deviceDirectory = hierarchy.getUserDeviceDirectory(userDevice);
  357.         deleteDirectory(deviceDirectory);
  358.     }

  359.     private static void writeLong(File target, long i) throws IOException {
  360.         if (target == null) {
  361.             throw new IOException("Could not write long to null-path.");
  362.         }

  363.         FileHierarchy.createFile(target);

  364.         try (DataOutputStream out = new DataOutputStream(new FileOutputStream(target))) {
  365.             out.writeLong(i);
  366.         }
  367.     }

  368.     private static Long readLong(File target) throws IOException {
  369.         if (target == null) {
  370.             throw new IOException("Could not read long from null-path.");
  371.         }

  372.         if (!target.exists() || !target.isFile()) {
  373.             return null;
  374.         }

  375.         try (DataInputStream in = new DataInputStream(new FileInputStream(target))) {
  376.             return in.readLong();
  377.         }
  378.     }

  379.     private static void writeBytes(File target, byte[] bytes) throws IOException {
  380.         if (target == null) {
  381.             throw new IOException("Could not write bytes to null-path.");
  382.         }

  383.         // Create file
  384.         FileHierarchy.createFile(target);

  385.         try (DataOutputStream out = new DataOutputStream(new FileOutputStream(target))) {
  386.             out.write(bytes);
  387.         }
  388.     }

  389.     private static byte[] readBytes(File target) throws IOException {
  390.         if (target == null) {
  391.             throw new IOException("Could not read bytes from null-path.");
  392.         }

  393.         if (!target.exists() || !target.isFile()) {
  394.             return null;
  395.         }

  396.         byte[] b = new byte[(int) target.length()];
  397.         try (DataInputStream in = new DataInputStream(new FileInputStream(target))) {
  398.             in.read(b);
  399.         }

  400.         return b;
  401.     }

  402.     private static void writeIntegers(File target, Set<Integer> integers) throws IOException {
  403.         if (target == null) {
  404.             throw new IOException("Could not write integers to null-path.");
  405.         }

  406.         FileHierarchy.createFile(target);

  407.         try (DataOutputStream out = new DataOutputStream(new FileOutputStream(target))) {
  408.             for (int i : integers) {
  409.                 out.writeInt(i);
  410.             }
  411.         }
  412.     }

  413.     private static Set<Integer> readIntegers(File target) throws IOException {
  414.         if (target == null) {
  415.             throw new IOException("Could not write integers to null-path.");
  416.         }

  417.         if (!target.exists() || !target.isFile()) {
  418.             return null;
  419.         }

  420.         HashSet<Integer> integers = new HashSet<>();

  421.         try (DataInputStream in = new DataInputStream(new FileInputStream(target))) {
  422.             while (true) {
  423.                 try {
  424.                     integers.add(in.readInt());
  425.                 } catch (EOFException e) {
  426.                     break;
  427.                 }
  428.             }
  429.         }

  430.         return integers;
  431.     }

  432.     /**
  433.      * Delete a directory with all subdirectories.
  434.      * @param root directory to be deleted
  435.      */
  436.     public static void deleteDirectory(File root) {
  437.         File[] currList;
  438.         Stack<File> stack = new Stack<>();
  439.         stack.push(root);
  440.         while (!stack.isEmpty()) {
  441.             if (stack.lastElement().isDirectory()) {
  442.                 currList = stack.lastElement().listFiles();
  443.                 if (currList != null && currList.length > 0) {
  444.                     for (File curr : currList) {
  445.                         stack.push(curr);
  446.                     }
  447.                 } else {
  448.                     stack.pop().delete();
  449.                 }
  450.             } else {
  451.                 stack.pop().delete();
  452.             }
  453.         }
  454.     }

  455.     /**
  456.      * This class represents the directory structure of the FileBasedOmemoStore.
  457.      * The directory looks as follows:
  458.      *
  459.      *  OMEMO_Store/
  460.      *      'romeo@montague.lit'/                           //Our bareJid
  461.      *          ...
  462.      *      'juliet@capulet.lit'/                           //Our other bareJid
  463.      *          '13371234'/                                 //deviceId
  464.      *              identityKeyPair                         //Our identityKeyPair
  465.      *              lastSignedPreKeyRenewal                 //Date of when the signedPreKey was last renewed.
  466.      *              preKeys/                                //Our preKeys
  467.      *                  '1'
  468.      *                  '2'
  469.      *                  ...
  470.      *              signedPreKeys/                          //Our signedPreKeys
  471.      *                  '1'
  472.      *                  '2'
  473.      *                  ...
  474.      *              contacts/
  475.      *                  'romeo@capulet.lit'/                //Juliets contact Romeo
  476.      *                      activeDevice                    //List of Romeos active devices
  477.      *                      inactiveDevices                 //List of his inactive devices
  478.      *                      'deviceId'/                     //Romeos deviceId
  479.      *                          identityKey                 //Romeos identityKey
  480.      *                          session                     //Our session with romeo
  481.      *                          trust                       //Records about the trust in romeos device
  482.      *                          (lastReceivedMessageDate)   //Only, for our own other devices:
  483.      *                                                          //date of the last received message
  484.      *
  485.      */
  486.     public static class FileHierarchy {

  487.         static final String STORE = "OMEMO_Store";
  488.         static final String CONTACTS = "contacts";
  489.         static final String IDENTITY_KEY = "identityKey";
  490.         static final String IDENTITY_KEY_PAIR = "identityKeyPair";
  491.         static final String PRE_KEYS = "preKeys";
  492.         static final String LAST_MESSAGE_RECEVIED_DATE = "lastMessageReceivedDate";
  493.         static final String LAST_DEVICEID_PUBLICATION_DATE = "lastDeviceIdPublicationDate";
  494.         static final String SIGNED_PRE_KEYS = "signedPreKeys";
  495.         static final String LAST_SIGNED_PRE_KEY_RENEWAL = "lastSignedPreKeyRenewal";
  496.         static final String SESSION = "session";
  497.         static final String DEVICE_LIST_ACTIVE = "activeDevices";
  498.         static final String DEVICE_LIST_INAVTIVE = "inactiveDevices";
  499.         static final String MESSAGE_COUNTER = "messageCounter";

  500.         File basePath;

  501.         FileHierarchy(File basePath) {
  502.             this.basePath = basePath;
  503.             basePath.mkdirs();
  504.         }

  505.         File getStoreDirectory() {
  506.             return createDirectory(basePath, STORE);
  507.         }

  508.         File getUserDirectory(OmemoDevice userDevice) {
  509.             return getUserDirectory(userDevice.getJid());
  510.         }

  511.         File getUserDirectory(BareJid bareJid) {
  512.             return createDirectory(getStoreDirectory(), bareJidEncoder.encode(bareJid));
  513.         }

  514.         File getUserDeviceDirectory(OmemoDevice userDevice) {
  515.             return createDirectory(getUserDirectory(userDevice.getJid()),
  516.                     Integer.toString(userDevice.getDeviceId()));
  517.         }

  518.         File getContactsDir(OmemoDevice userDevice) {
  519.             return createDirectory(getUserDeviceDirectory(userDevice), CONTACTS);
  520.         }

  521.         File getContactsDir(OmemoDevice userDevice, BareJid contact) {
  522.             return createDirectory(getContactsDir(userDevice), bareJidEncoder.encode(contact));
  523.         }

  524.         File getContactsDir(OmemoDevice userDevice, OmemoDevice contactsDevice) {
  525.             return createDirectory(getContactsDir(userDevice, contactsDevice.getJid()),
  526.                     Integer.toString(contactsDevice.getDeviceId()));
  527.         }

  528.         File getIdentityKeyPairPath(OmemoDevice userDevice) {
  529.             return new File(getUserDeviceDirectory(userDevice), IDENTITY_KEY_PAIR);
  530.         }

  531.         File getPreKeysDirectory(OmemoDevice userDevice) {
  532.             return createDirectory(getUserDeviceDirectory(userDevice), PRE_KEYS);
  533.         }

  534.         File getPreKeyPath(OmemoDevice userDevice, int preKeyId) {
  535.             return new File(getPreKeysDirectory(userDevice), Integer.toString(preKeyId));
  536.         }

  537.         File getLastMessageReceivedDatePath(OmemoDevice userDevice, OmemoDevice device) {
  538.             return new File(getContactsDir(userDevice, device), LAST_MESSAGE_RECEVIED_DATE);
  539.         }

  540.         File getLastDeviceIdPublicationDatePath(OmemoDevice userDevice, OmemoDevice device) {
  541.             return new File(getContactsDir(userDevice, device), LAST_DEVICEID_PUBLICATION_DATE);
  542.         }

  543.         File getSignedPreKeysDirectory(OmemoDevice userDevice) {
  544.             return createDirectory(getUserDeviceDirectory(userDevice), SIGNED_PRE_KEYS);
  545.         }

  546.         File getLastSignedPreKeyRenewal(OmemoDevice userDevice) {
  547.             return new File(getUserDeviceDirectory(userDevice), LAST_SIGNED_PRE_KEY_RENEWAL);
  548.         }

  549.         File getContactsIdentityKeyPath(OmemoDevice userDevice, OmemoDevice contactsDevice) {
  550.             return new File(getContactsDir(userDevice, contactsDevice), IDENTITY_KEY);

  551.         }

  552.         File getContactsSessionPath(OmemoDevice userDevice, OmemoDevice contactsDevice) {
  553.             return new File(getContactsDir(userDevice, contactsDevice), SESSION);
  554.         }

  555.         File getContactsActiveDevicesPath(OmemoDevice userDevice, BareJid contact) {
  556.             return new File(getContactsDir(userDevice, contact), DEVICE_LIST_ACTIVE);
  557.         }

  558.         File getContactsInactiveDevicesPath(OmemoDevice userDevice, BareJid contact) {
  559.             return new File(getContactsDir(userDevice, contact), DEVICE_LIST_INAVTIVE);
  560.         }

  561.         File getDevicesMessageCounterPath(OmemoDevice userDevice, OmemoDevice otherDevice) {
  562.             return new File(getContactsDir(userDevice, otherDevice), MESSAGE_COUNTER);
  563.         }

  564.         private static File createFile(File f) throws IOException {
  565.             File p = f.getParentFile();
  566.             createDirectory(p);
  567.             f.createNewFile();
  568.             return f;

  569.         }

  570.         private static File createDirectory(File dir, String subdir) {
  571.             File f = new File(dir, subdir);
  572.             return createDirectory(f);
  573.         }

  574.         private static File createDirectory(File f) {
  575.             if (f.exists() && f.isDirectory()) {
  576.                 return f;
  577.             }

  578.             f.mkdirs();
  579.             return f;
  580.         }
  581.     }

  582.     /**
  583.      * Convert {@link BareJid BareJids} to Strings using the legacy {@link BareJid#toString()} method instead of the
  584.      * proper, url safe {@link BareJid#asUrlEncodedString()} method.
  585.      * While it is highly advised to use the new format, you can use this method to stay backwards compatible to data
  586.      * sets created by the old implementation.
  587.      */
  588.     @SuppressWarnings("deprecation")
  589.     public static void useLegacyBareJidEncoding() {
  590.         bareJidEncoder = new BareJidEncoder.LegacyEncoder();
  591.     }
  592. }