/** * * Copyright 2017 Paul Schaub * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.jivesoftware.smackx.omemo; import java.io.DataInputStream; import java.io.DataOutputStream; import java.io.EOFException; import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; import java.util.Date; import java.util.HashMap; import java.util.HashSet; import java.util.Set; import java.util.Stack; import java.util.logging.Level; import java.util.logging.Logger; import org.jivesoftware.smack.util.StringUtils; import org.jivesoftware.smackx.omemo.exceptions.CorruptedOmemoKeyException; import org.jivesoftware.smackx.omemo.internal.CachedDeviceList; import org.jivesoftware.smackx.omemo.internal.OmemoDevice; import org.jxmpp.jid.BareJid; /** * Like a rocket! * * @author Paul Schaub */ public abstract class FileBasedOmemoStore extends OmemoStore { private static final Logger LOGGER = Logger.getLogger(FileBasedOmemoStore.class.getSimpleName()); private final FileHierarchy hierarchy; public FileBasedOmemoStore() { this(OmemoConfiguration.getFileBasedOmemoStoreDefaultPath()); } public FileBasedOmemoStore(File basePath) { super(); if (basePath == null) { throw new IllegalStateException("No FileBasedOmemoStoreDefaultPath set in OmemoConfiguration."); } this.hierarchy = new FileHierarchy(basePath); } @Override public boolean isFreshInstallation(OmemoManager omemoManager) { File userDirectory = hierarchy.getUserDeviceDirectory(omemoManager); File[] files = userDirectory.listFiles(); return files == null || files.length == 0; } @Override public int getDefaultDeviceId(BareJid user) { try { return readInt(hierarchy.getDefaultDeviceIdPath(user)); } catch (IOException e) { return -1; } } @Override public void setDefaultDeviceId(BareJid user, int defaultDeviceId) { File defaultDeviceIdPath = hierarchy.getDefaultDeviceIdPath(user); if (defaultDeviceIdPath == null) { LOGGER.log(Level.SEVERE, "defaultDeviceIdPath is null!"); } try { writeInt(defaultDeviceIdPath, defaultDeviceId); } catch (IOException e) { LOGGER.log(Level.SEVERE, "Could not write defaultDeviceId: " + e, e); } } @Override public int loadLastPreKeyId(OmemoManager omemoManager) { try { int l = readInt(hierarchy.getLastPreKeyIdPath(omemoManager)); return l == -1 ? 0 : l; } catch (IOException e) { return 0; } } @Override public void storeLastPreKeyId(OmemoManager omemoManager, int currentPreKeyId) { try { writeInt(hierarchy.getLastPreKeyIdPath(omemoManager), currentPreKeyId); } catch (IOException e) { LOGGER.log(Level.SEVERE, "Could not write lastPreKeyId: " + e, e); } } @Override public T_IdKeyPair loadOmemoIdentityKeyPair(OmemoManager omemoManager) throws CorruptedOmemoKeyException { File identityKeyPairPath = hierarchy.getIdentityKeyPairPath(omemoManager); try { byte[] bytes = readBytes(identityKeyPairPath); return bytes != null ? keyUtil().identityKeyPairFromBytes(bytes) : null; } catch (IOException e) { return null; } } @Override public void storeOmemoIdentityKeyPair(OmemoManager omemoManager, T_IdKeyPair identityKeyPair) { File identityKeyPairPath = hierarchy.getIdentityKeyPairPath(omemoManager); try { writeBytes(identityKeyPairPath, keyUtil().identityKeyPairToBytes(identityKeyPair)); } catch (IOException e) { LOGGER.log(Level.SEVERE, "Could not write omemoIdentityKeyPair: " + e, e); } } @Override public T_IdKey loadOmemoIdentityKey(OmemoManager omemoManager, OmemoDevice device) throws CorruptedOmemoKeyException { File identityKeyPath = hierarchy.getContactsIdentityKeyPath(omemoManager, device); try { byte[] bytes = readBytes(identityKeyPath); return bytes != null ? keyUtil().identityKeyFromBytes(bytes) : null; } catch (IOException e) { return null; } } @Override public void storeOmemoIdentityKey(OmemoManager omemoManager, OmemoDevice device, T_IdKey t_idKey) { File identityKeyPath = hierarchy.getContactsIdentityKeyPath(omemoManager, device); try { writeBytes(identityKeyPath, keyUtil().identityKeyToBytes(t_idKey)); } catch (IOException e) { LOGGER.log(Level.SEVERE, "Could not write omemoIdentityKey of " + device + ": " + e, e); } } @Override public boolean isTrustedOmemoIdentity(OmemoManager omemoManager, OmemoDevice device, OmemoFingerprint fingerprint) { File trustPath = hierarchy.getContactsTrustPath(omemoManager, device); try { String depositedFingerprint = new String(readBytes(trustPath), StringUtils.UTF8); return depositedFingerprint.length() > 2 && depositedFingerprint.charAt(0) == '1' && new OmemoFingerprint(depositedFingerprint.substring(2)).equals(fingerprint); } catch (IOException e) { return false; } } @Override public boolean isDecidedOmemoIdentity(OmemoManager omemoManager, OmemoDevice device, OmemoFingerprint fingerprint) { File trustPath = hierarchy.getContactsTrustPath(omemoManager, device); try { String depositedFingerprint = new String(readBytes(trustPath), StringUtils.UTF8); return depositedFingerprint.length() > 2 && (depositedFingerprint.charAt(0) == '1' || depositedFingerprint.charAt(0) == '2') && new OmemoFingerprint(depositedFingerprint.substring(2)).equals(fingerprint); } catch (IOException e) { return false; } } @Override public void trustOmemoIdentity(OmemoManager omemoManager, OmemoDevice device, OmemoFingerprint fingerprint) { File trustPath = hierarchy.getContactsTrustPath(omemoManager, device); try { writeBytes(trustPath, ("1 " + fingerprint.toString()).getBytes(StringUtils.UTF8)); } catch (IOException e) { LOGGER.log(Level.SEVERE, "Could not trust " + device + ": " + e, e); } } @Override public void distrustOmemoIdentity(OmemoManager omemoManager, OmemoDevice device, OmemoFingerprint fingerprint) { File trustPath = hierarchy.getContactsTrustPath(omemoManager, device); try { writeBytes(trustPath, ("2 " + fingerprint.toString()).getBytes(StringUtils.UTF8)); } catch (IOException e) { LOGGER.log(Level.SEVERE, "Could not distrust " + device + ": " + e, e); } } @Override public void setDateOfLastReceivedMessage(OmemoManager omemoManager, OmemoDevice from, Date date) { File lastMessageReceived = hierarchy.getLastMessageReceivedDatePath(omemoManager, from); try { writeLong(lastMessageReceived, date.getTime()); } catch (IOException e) { LOGGER.log(Level.SEVERE, "Could not write date of last received message from " + from + ": " + e, e); } } @Override public Date getDateOfLastReceivedMessage(OmemoManager omemoManager, OmemoDevice from) { File lastMessageReceived = hierarchy.getLastMessageReceivedDatePath(omemoManager, from); try { long date = readLong(lastMessageReceived); return date != -1 ? new Date(date) : null; } catch (IOException e) { return null; } } @Override public void setDateOfLastSignedPreKeyRenewal(OmemoManager omemoManager, Date date) { File lastSignedPreKeyRenewal = hierarchy.getLastSignedPreKeyRenewal(omemoManager); try { writeLong(lastSignedPreKeyRenewal, date.getTime()); } catch (IOException e) { LOGGER.log(Level.SEVERE, "Could not write date of last singedPreKey renewal for " + omemoManager.getOwnDevice() + ": " + e, e); } } @Override public Date getDateOfLastSignedPreKeyRenewal(OmemoManager omemoManager) { File lastSignedPreKeyRenewal = hierarchy.getLastSignedPreKeyRenewal(omemoManager); try { long date = readLong(lastSignedPreKeyRenewal); return date != -1 ? new Date(date) : null; } catch (IOException e) { return null; } } @Override public T_PreKey loadOmemoPreKey(OmemoManager omemoManager, int preKeyId) { File preKeyPath = hierarchy.getPreKeyPath(omemoManager, preKeyId); try { byte[] bytes = readBytes(preKeyPath); return bytes != null ? keyUtil().preKeyFromBytes(bytes) : null; } catch (IOException e) { return null; } } @Override public void storeOmemoPreKey(OmemoManager omemoManager, int preKeyId, T_PreKey t_preKey) { File preKeyPath = hierarchy.getPreKeyPath(omemoManager, preKeyId); try { writeBytes(preKeyPath, keyUtil().preKeyToBytes(t_preKey)); } catch (IOException e) { LOGGER.log(Level.SEVERE, "Could not write preKey with id " + preKeyId + ": " + e, e); } } @Override public void removeOmemoPreKey(OmemoManager omemoManager, int preKeyId) { File preKeyPath = hierarchy.getPreKeyPath(omemoManager, preKeyId); preKeyPath.delete(); } @Override public int loadCurrentSignedPreKeyId(OmemoManager omemoManager) { File currentSignedPreKeyIdPath = hierarchy.getCurrentSignedPreKeyIdPath(omemoManager); try { int i = readInt(currentSignedPreKeyIdPath); return i == -1 ? 0 : i; } catch (IOException e) { return 0; } } @Override public void storeCurrentSignedPreKeyId(OmemoManager omemoManager, int currentSignedPreKeyId) { File currentSignedPreKeyIdPath = hierarchy.getCurrentSignedPreKeyIdPath(omemoManager); try { writeInt(currentSignedPreKeyIdPath, currentSignedPreKeyId); } catch (IOException e) { LOGGER.log(Level.SEVERE, "Could not write currentSignedPreKeyId " + currentSignedPreKeyId + " for " + omemoManager.getOwnDevice() + ": " + e, e); } } @Override public HashMap loadOmemoPreKeys(OmemoManager omemoManager) { File preKeyDirectory = hierarchy.getPreKeysDirectory(omemoManager); HashMap preKeys = new HashMap<>(); if (preKeyDirectory == null) { return preKeys; } File[] keys = preKeyDirectory.listFiles(); for (File f : keys != null ? keys : new File[0]) { try { byte[] bytes = readBytes(f); if (bytes == null) { continue; } T_PreKey p = keyUtil().preKeyFromBytes(bytes); preKeys.put(Integer.parseInt(f.getName()), p); } catch (IOException e) { // Do nothing. } } return preKeys; } @Override public T_SigPreKey loadOmemoSignedPreKey(OmemoManager omemoManager, int signedPreKeyId) { File signedPreKeyPath = new File(hierarchy.getSignedPreKeysDirectory(omemoManager), Integer.toString(signedPreKeyId)); try { byte[] bytes = readBytes(signedPreKeyPath); return bytes != null ? keyUtil().signedPreKeyFromBytes(bytes) : null; } catch (IOException e) { return null; } } @Override public HashMap loadOmemoSignedPreKeys(OmemoManager omemoManager) { File signedPreKeysDirectory = hierarchy.getSignedPreKeysDirectory(omemoManager); HashMap signedPreKeys = new HashMap<>(); if (signedPreKeysDirectory == null) { return signedPreKeys; } File[] keys = signedPreKeysDirectory.listFiles(); for (File f : keys != null ? keys : new File[0]) { try { byte[] bytes = readBytes(f); if (bytes == null) { continue; } T_SigPreKey p = keyUtil().signedPreKeyFromBytes(bytes); signedPreKeys.put(Integer.parseInt(f.getName()), p); } catch (IOException e) { // Do nothing. } } return signedPreKeys; } @Override public void storeOmemoSignedPreKey(OmemoManager omemoManager, int signedPreKeyId, T_SigPreKey signedPreKey) { File signedPreKeyPath = new File(hierarchy.getSignedPreKeysDirectory(omemoManager), Integer.toString(signedPreKeyId)); try { writeBytes(signedPreKeyPath, keyUtil().signedPreKeyToBytes(signedPreKey)); } catch (IOException e) { LOGGER.log(Level.SEVERE, "Could not write signedPreKey " + signedPreKey + " for " + omemoManager.getOwnDevice() + ": " + e, e); } } @Override public void removeOmemoSignedPreKey(OmemoManager omemoManager, int signedPreKeyId) { File signedPreKeyPath = new File(hierarchy.getSignedPreKeysDirectory(omemoManager), Integer.toString(signedPreKeyId)); signedPreKeyPath.delete(); } @Override public T_Sess loadRawSession(OmemoManager omemoManager, OmemoDevice device) { File sessionPath = hierarchy.getContactsSessionPath(omemoManager, device); try { byte[] bytes = readBytes(sessionPath); return bytes != null ? keyUtil().rawSessionFromBytes(bytes) : null; } catch (IOException e) { return null; } } @Override public HashMap loadAllRawSessionsOf(OmemoManager omemoManager, BareJid contact) { File contactsDirectory = hierarchy.getContactsDir(omemoManager, contact); HashMap sessions = new HashMap<>(); String[] devices = contactsDirectory.list(); for (String deviceId : devices != null ? devices : new String[0]) { int id; try { id = Integer.parseInt(deviceId); } catch (NumberFormatException e) { continue; } OmemoDevice device = new OmemoDevice(contact, id); File session = hierarchy.getContactsSessionPath(omemoManager, device); try { byte[] bytes = readBytes(session); if (bytes == null) { continue; } T_Sess s = keyUtil().rawSessionFromBytes(bytes); sessions.put(id, s); } catch (IOException e) { // Do nothing. } } return sessions; } @Override public void storeRawSession(OmemoManager omemoManager, OmemoDevice device, T_Sess session) { File sessionPath = hierarchy.getContactsSessionPath(omemoManager, device); try { writeBytes(sessionPath, keyUtil().rawSessionToBytes(session)); } catch (IOException e) { LOGGER.log(Level.SEVERE, "Could not write session between our device " + omemoManager.getOwnDevice() + " and their device " + device + ": " + e.getMessage()); } } @Override public void removeRawSession(OmemoManager omemoManager, OmemoDevice device) { File sessionPath = hierarchy.getContactsSessionPath(omemoManager, device); sessionPath.delete(); } @Override public void removeAllRawSessionsOf(OmemoManager omemoManager, BareJid contact) { File contactsDirectory = hierarchy.getContactsDir(omemoManager, contact); String[] devices = contactsDirectory.list(); for (String deviceId : devices != null ? devices : new String[0]) { int id; try { id = Integer.parseInt(deviceId); } catch (NumberFormatException e) { continue; } OmemoDevice device = new OmemoDevice(contact, id); File session = hierarchy.getContactsSessionPath(omemoManager, device); session.delete(); } } @Override public boolean containsRawSession(OmemoManager omemoManager, OmemoDevice device) { File session = hierarchy.getContactsSessionPath(omemoManager, device); return session.exists(); } @Override public CachedDeviceList loadCachedDeviceList(OmemoManager omemoManager, BareJid contact) { CachedDeviceList cachedDeviceList = new CachedDeviceList(); if (contact == null) { return null; } // active File activeDevicesPath = hierarchy.getContactsActiveDevicesPath(omemoManager, contact); try { cachedDeviceList.getActiveDevices().addAll(readIntegers(activeDevicesPath)); } catch (IOException e) { // Don't worry... } // inactive File inactiveDevicesPath = hierarchy.getContactsInactiveDevicesPath(omemoManager, contact); try { cachedDeviceList.getInactiveDevices().addAll(readIntegers(inactiveDevicesPath)); } catch (IOException e) { // It's ok :) } return cachedDeviceList; } @Override public void storeCachedDeviceList(OmemoManager omemoManager, BareJid contact, CachedDeviceList deviceList) { if (contact == null) { return; } File activeDevices = hierarchy.getContactsActiveDevicesPath(omemoManager, contact); try { writeIntegers(activeDevices, deviceList.getActiveDevices()); } catch (IOException e) { LOGGER.log(Level.SEVERE, "Could not write active devices of deviceList of " + contact + ": " + e.getMessage()); } File inactiveDevices = hierarchy.getContactsInactiveDevicesPath(omemoManager, contact); try { writeIntegers(inactiveDevices, deviceList.getInactiveDevices()); } catch (IOException e) { LOGGER.log(Level.SEVERE, "Could not write inactive devices of deviceList of " + contact + ": " + e.getMessage()); } } @Override public void purgeOwnDeviceKeys(OmemoManager omemoManager) { File deviceDirectory = hierarchy.getUserDeviceDirectory(omemoManager); deleteDirectory(deviceDirectory); } private void writeInt(File target, int i) throws IOException { if (target == null) { throw new IOException("Could not write integer to null-path."); } FileHierarchy.createFile(target); IOException io = null; DataOutputStream out = null; try { out = new DataOutputStream(new FileOutputStream(target)); out.writeInt(i); } catch (IOException e) { io = e; } finally { if (out != null) { out.close(); } } if (io != null) { throw io; } } private int readInt(File target) throws IOException { if (target == null) { throw new IOException("Could not read integer from null-path."); } IOException io = null; int i = -1; DataInputStream in = null; try { in = new DataInputStream(new FileInputStream(target)); i = in.readInt(); } catch (IOException e) { io = e; } finally { if (in != null) { in.close(); } } if (io != null) { throw io; } return i; } private void writeLong(File target, long i) throws IOException { if (target == null) { throw new IOException("Could not write long to null-path."); } FileHierarchy.createFile(target); IOException io = null; DataOutputStream out = null; try { out = new DataOutputStream(new FileOutputStream(target)); out.writeLong(i); } catch (IOException e) { io = e; } finally { if (out != null) { out.close(); } } if (io != null) { throw io; } } private long readLong(File target) throws IOException { if (target == null) { throw new IOException("Could not read long from null-path."); } IOException io = null; long l = -1; DataInputStream in = null; try { in = new DataInputStream(new FileInputStream(target)); l = in.readLong(); } catch (IOException e) { io = e; } finally { if (in != null) { in.close(); } } if (io != null) { throw io; } return l; } private void writeBytes(File target, byte[] bytes) throws IOException { if (target == null) { throw new IOException("Could not write bytes to null-path."); } // Create file FileHierarchy.createFile(target); IOException io = null; DataOutputStream out = null; try { out = new DataOutputStream(new FileOutputStream(target)); out.write(bytes); } catch (IOException e) { io = e; } finally { if (out != null) { out.close(); } } if (io != null) { throw io; } } private byte[] readBytes(File target) throws IOException { if (target == null) { throw new IOException("Could not read bytes from null-path."); } byte[] b = null; IOException io = null; DataInputStream in = null; try { in = new DataInputStream(new FileInputStream(target)); b = new byte[in.available()]; in.read(b); } catch (IOException e) { io = e; } finally { if (in != null) { in.close(); } } if (io != null) { throw io; } return b; } private void writeIntegers(File target, Set integers) throws IOException { if (target == null) { throw new IOException("Could not write integers to null-path."); } IOException io = null; DataOutputStream out = null; try { out = new DataOutputStream(new FileOutputStream(target)); for (int i : integers) { out.writeInt(i); } } catch (IOException e) { io = e; } finally { if (out != null) { out.close(); } } if (io != null) { throw io; } } private Set readIntegers(File target) throws IOException { if (target == null) { throw new IOException("Could not write integers to null-path."); } HashSet integers = new HashSet<>(); IOException io = null; DataInputStream in = null; try { in = new DataInputStream(new FileInputStream(target)); try { while (true) { integers.add(in.readInt()); } } catch (EOFException e) { // Reached end of the list. } } catch (IOException e) { io = e; } finally { if (in != null) { in.close(); } } if (io != null) { throw io; } return integers; } public static void deleteDirectory(File root) { File[] currList; Stack stack = new Stack<>(); stack.push(root); while (!stack.isEmpty()) { if (stack.lastElement().isDirectory()) { currList = stack.lastElement().listFiles(); if (currList != null && currList.length > 0) { for (File curr : currList) { stack.push(curr); } } else { stack.pop().delete(); } } else { stack.pop().delete(); } } } /** * This class represents the directory structure of the FileBasedOmemoStoreV2. * The directory looks as follows: * * OMEMO_Store/ * 'romeo@montague.lit'/ //Our bareJid * ... * 'juliet@capulet.lit'/ //Our other bareJid * defaultDeviceId * '13371234'/ //deviceId * identityKeyPair //Our identityKeyPair * lastPreKeyId //Id of the last preKey we generated * currentSignedPreKeyId //Id of the currently used signedPreKey * lastSignedPreKeyRenewal //Date of when the signedPreKey was last renewed. * preKeys/ //Our preKeys * '1' * '2' * ... * signedPreKeys/ //Our signedPreKeys * '1' * '2' * ... * contacts/ * 'romeo@capulet.lit'/ //Juliets contact Romeo * activeDevice //List of Romeos active devices * inactiveDevices //List of his inactive devices * 'deviceId'/ //Romeos deviceId * identityKey //Romeos identityKey * session //Our session with romeo * trust //Records about the trust in romeos device * (lastReceivedMessageDate) //Only, for our own other devices: * //date of the last received message * */ public static class FileHierarchy { static final String STORE = "OMEMO_Store"; static final String CONTACTS = "contacts"; static final String DEFAULT_DEVICE_ID = "defaultDeviceId"; static final String IDENTITY_KEY = "identityKey"; static final String IDENTITY_KEY_PAIR = "identityKeyPair"; static final String PRE_KEYS = "preKeys"; static final String LAST_MESSAGE_RECEVIED_DATE = "lastMessageReceivedDate"; static final String LAST_PRE_KEY_ID = "lastPreKeyId"; static final String SIGNED_PRE_KEYS = "signedPreKeys"; static final String CURRENT_SIGNED_PRE_KEY_ID = "currentSignedPreKeyId"; static final String LAST_SIGNED_PRE_KEY_RENEWAL = "lastSignedPreKeyRenewal"; static final String SESSION = "session"; static final String DEVICE_LIST_ACTIVE = "activeDevices"; static final String DEVICE_LIST_INAVTIVE = "inactiveDevices"; static final String TRUST = "trust"; File basePath; FileHierarchy(File basePath) { this.basePath = basePath; basePath.mkdirs(); } File getStoreDirectory() { return createDirectory(basePath, STORE); } File getUserDirectory(BareJid bareJid) { return createDirectory(getStoreDirectory(), bareJid.toString()); } File getUserDeviceDirectory(OmemoManager omemoManager) { return createDirectory(getUserDirectory(omemoManager.getOwnJid()), Integer.toString(omemoManager.getDeviceId())); } File getContactsDir(OmemoManager omemoManager) { return createDirectory(getUserDeviceDirectory(omemoManager), CONTACTS); } File getContactsDir(OmemoManager omemoManager, BareJid contact) { return createDirectory(getContactsDir(omemoManager), contact.toString()); } File getContactsDir(OmemoManager omemoManager, OmemoDevice omemoDevice) { return createDirectory(getContactsDir(omemoManager, omemoDevice.getJid()), Integer.toString(omemoDevice.getDeviceId())); } File getIdentityKeyPairPath(OmemoManager omemoManager) { return new File(getUserDeviceDirectory(omemoManager), IDENTITY_KEY_PAIR); } File getPreKeysDirectory(OmemoManager omemoManager) { return createDirectory(getUserDeviceDirectory(omemoManager), PRE_KEYS); } File getPreKeyPath(OmemoManager omemoManager, int preKeyId) { return new File(getPreKeysDirectory(omemoManager), Integer.toString(preKeyId)); } File getLastMessageReceivedDatePath(OmemoManager omemoManager, OmemoDevice device) { return new File(getContactsDir(omemoManager, device), LAST_MESSAGE_RECEVIED_DATE); } File getLastPreKeyIdPath(OmemoManager omemoManager) { return new File(getUserDeviceDirectory(omemoManager), LAST_PRE_KEY_ID); } File getSignedPreKeysDirectory(OmemoManager omemoManager) { return createDirectory(getUserDeviceDirectory(omemoManager), SIGNED_PRE_KEYS); } File getCurrentSignedPreKeyIdPath(OmemoManager omemoManager) { return new File(getUserDeviceDirectory(omemoManager), CURRENT_SIGNED_PRE_KEY_ID); } File getLastSignedPreKeyRenewal(OmemoManager omemoManager) { return new File(getUserDeviceDirectory(omemoManager), LAST_SIGNED_PRE_KEY_RENEWAL); } File getDefaultDeviceIdPath(BareJid bareJid) { return new File(getUserDirectory(bareJid), DEFAULT_DEVICE_ID); } File getContactsIdentityKeyPath(OmemoManager omemoManager, OmemoDevice omemoDevice) { return new File(getContactsDir(omemoManager, omemoDevice), IDENTITY_KEY); } File getContactsSessionPath(OmemoManager omemoManager, OmemoDevice omemoDevice) { return new File(getContactsDir(omemoManager, omemoDevice), SESSION); } File getContactsActiveDevicesPath(OmemoManager omemoManager, BareJid contact) { return new File(getContactsDir(omemoManager, contact), DEVICE_LIST_ACTIVE); } File getContactsInactiveDevicesPath(OmemoManager omemoManager, BareJid contact) { return new File(getContactsDir(omemoManager, contact), DEVICE_LIST_INAVTIVE); } File getContactsTrustPath(OmemoManager omemoManager, OmemoDevice omemoDevice) { return new File(getContactsDir(omemoManager, omemoDevice), TRUST); } private static File createFile(File f) throws IOException { File p = f.getParentFile(); createDirectory(p); f.createNewFile(); return f; } private static File createFile(File dir, String filename) throws IOException { return createFile(new File(dir, filename)); } private static File createDirectory(File dir, String subdir) { File f = new File(dir, subdir); return createDirectory(f); } private static File createDirectory(File f) { if (f.exists() && f.isDirectory()) { return f; } f.mkdirs(); return f; } } }