1
0
Fork 0
mirror of https://github.com/vanitasvitae/Smack.git synced 2024-06-17 17:04:49 +02:00
Smack/smack-omemo/src/main/java/org/jivesoftware/smackx/omemo/FileBasedOmemoStore.java
Paul Schaub 1f731f6318
Rework support for XEP-0384: OMEMO Encryption
Changes:

    Rework integration tests
    New structure of base integration test classes
    bump dependency on signal-protocol-java from 2.4.0 to 2.6.2
    Introduced CachingOmemoStore implementations
    Use CachingOmemoStore classes in integration tests
    Removed OmemoSession classes (replaced with more logical OmemoRatchet classes)
    Consequently also removed load/storeOmemoSession methods from OmemoStore
    Removed some clutter from KeyUtil classes
    Moved trust decision related code from OmemoStore to TrustCallback
    Require authenticated connection for many functions
    Add async initialization function in OmemoStore
    Refactor omemo test package (/java/org/jivesoftware/smack/omemo -> /java/org/jivesoftware/smackx)
    Remove OmemoStore method isFreshInstallation() as well as defaultDeviceId related stuff
    FileBasedOmemoStore: Add cleaner methods to store/load base data types (Using tryWithResource, only for future releases, once Android API gets bumped)
    Attempt to make OmemoManager thread safe
    new logic for getInstanceFor() deviceId determination
    OmemoManagers encrypt methods now don't throw exceptions when encryption for some devices fails. Instead message gets encrypted when possible and more information about failures gets returned alongside the message itself
    Added OmemoMessage class for that purpose
    Reworked entire OmemoService class
    Use safer logic for creating trust-ignoring messages (like ratchet-update messages)
    Restructure elements/provider in order to prepare for OMEMO namespace bumps
    Remove OmemoManager.regenerate() methods in favor of getInstanceFor(connection, randomDeviceId)
    Removed some unnecessary configuration options
    Prepare for support of more AES message key types
    Simplify session creation
    Where possible, avoid side effects in methods
    Add UntrustedOmemoIdentityException
    Add TrustState enum
    More improved tests
2018-06-13 12:29:16 +02:00

879 lines
31 KiB
Java

/**
*
* 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.FileNotFoundException;
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.SortedSet;
import java.util.Stack;
import java.util.TreeMap;
import java.util.TreeSet;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.jivesoftware.smackx.omemo.exceptions.CorruptedOmemoKeyException;
import org.jivesoftware.smackx.omemo.internal.OmemoCachedDeviceList;
import org.jivesoftware.smackx.omemo.internal.OmemoDevice;
import org.jxmpp.jid.BareJid;
/**
* Like a rocket!
*
* @author Paul Schaub
*/
public abstract class FileBasedOmemoStore<T_IdKeyPair, T_IdKey, T_PreKey, T_SigPreKey, T_Sess, T_Addr, T_ECPub, T_Bundle, T_Ciph>
extends OmemoStore<T_IdKeyPair, T_IdKey, T_PreKey, T_SigPreKey, T_Sess, T_Addr, T_ECPub, T_Bundle, T_Ciph> {
private final FileHierarchy hierarchy;
private static final Logger LOGGER = Logger.getLogger(FileBasedOmemoStore.class.getName());
public FileBasedOmemoStore(File basePath) {
super();
if (basePath == null) {
throw new IllegalStateException("No FileBasedOmemoStoreDefaultPath set in OmemoConfiguration.");
}
this.hierarchy = new FileHierarchy(basePath);
}
@Override
public T_IdKeyPair loadOmemoIdentityKeyPair(OmemoDevice userDevice)
throws CorruptedOmemoKeyException {
File identityKeyPairPath = hierarchy.getIdentityKeyPairPath(userDevice);
return keyUtil().identityKeyPairFromBytes(readBytes(identityKeyPairPath));
}
@Override
public void storeOmemoIdentityKeyPair(OmemoDevice userDevice, T_IdKeyPair identityKeyPair) {
File identityKeyPairPath = hierarchy.getIdentityKeyPairPath(userDevice);
writeBytes(identityKeyPairPath, keyUtil().identityKeyPairToBytes(identityKeyPair));
}
@Override
public void removeOmemoIdentityKeyPair(OmemoDevice userDevice) {
File identityKeyPairPath = hierarchy.getIdentityKeyPairPath(userDevice);
if (!identityKeyPairPath.delete()) {
LOGGER.log(Level.WARNING, "Could not delete OMEMO IdentityKeyPair " + identityKeyPairPath.getAbsolutePath());
}
}
@Override
public T_IdKey loadOmemoIdentityKey(OmemoDevice userDevice, OmemoDevice contactsDevice)
throws CorruptedOmemoKeyException {
File identityKeyPath = hierarchy.getContactsIdentityKeyPath(userDevice, contactsDevice);
byte[] bytes = readBytes(identityKeyPath);
return bytes != null ? keyUtil().identityKeyFromBytes(bytes) : null;
}
@Override
public void storeOmemoIdentityKey(OmemoDevice userDevice, OmemoDevice contactsDevice, T_IdKey t_idKey) {
File identityKeyPath = hierarchy.getContactsIdentityKeyPath(userDevice, contactsDevice);
writeBytes(identityKeyPath, keyUtil().identityKeyToBytes(t_idKey));
}
@Override
public void removeOmemoIdentityKey(OmemoDevice userDevice, OmemoDevice contactsDevice) {
File identityKeyPath = hierarchy.getContactsIdentityKeyPath(userDevice, contactsDevice);
if (!identityKeyPath.delete()) {
LOGGER.log(Level.WARNING, "Could not delete OMEMO identityKey " + identityKeyPath.getAbsolutePath());
}
}
@Override
public SortedSet<Integer> localDeviceIdsOf(BareJid localUser) {
SortedSet<Integer> deviceIds = new TreeSet<>();
File userDir = hierarchy.getUserDirectory(localUser);
File[] list = userDir.listFiles();
for (File d : (list != null ? list : new File[] {})) {
if (d.isDirectory()) {
try {
deviceIds.add(Integer.parseInt(d.getName()));
} catch (NumberFormatException e) {
// ignore
}
}
}
return deviceIds;
}
@Override
public void setDateOfLastReceivedMessage(OmemoDevice userDevice, OmemoDevice contactsDevice, Date date) {
File lastMessageReceived = hierarchy.getLastMessageReceivedDatePath(userDevice, contactsDevice);
writeLong(lastMessageReceived, date.getTime());
}
@Override
public Date getDateOfLastReceivedMessage(OmemoDevice userDevice, OmemoDevice contactsDevice) {
File lastMessageReceived = hierarchy.getLastMessageReceivedDatePath(userDevice, contactsDevice);
Long date = readLong(lastMessageReceived);
return date != null ? new Date(date) : null;
}
@Override
public void setDateOfLastDeviceIdPublication(OmemoDevice userDevice, OmemoDevice contactsDevice, Date date) {
File lastDeviceIdPublished = hierarchy.getLastDeviceIdPublicationDatePath(userDevice, contactsDevice);
writeLong(lastDeviceIdPublished, date.getTime());
}
@Override
public Date getDateOfLastDeviceIdPublication(OmemoDevice userDevice, OmemoDevice contactsDevice) {
File lastDeviceIdPublished = hierarchy.getLastDeviceIdPublicationDatePath(userDevice, contactsDevice);
Long date = readLong(lastDeviceIdPublished);
return date != null ? new Date(date) : null;
}
@Override
public void setDateOfLastSignedPreKeyRenewal(OmemoDevice userDevice, Date date) {
File lastSignedPreKeyRenewal = hierarchy.getLastSignedPreKeyRenewal(userDevice);
writeLong(lastSignedPreKeyRenewal, date.getTime());
}
@Override
public Date getDateOfLastSignedPreKeyRenewal(OmemoDevice userDevice) {
File lastSignedPreKeyRenewal = hierarchy.getLastSignedPreKeyRenewal(userDevice);
Long date = readLong(lastSignedPreKeyRenewal);
return date != null ? new Date(date) : null;
}
@Override
public T_PreKey loadOmemoPreKey(OmemoDevice userDevice, int preKeyId) {
File preKeyPath = hierarchy.getPreKeyPath(userDevice, preKeyId);
byte[] bytes = readBytes(preKeyPath);
if (bytes != null) {
try {
return keyUtil().preKeyFromBytes(bytes);
} catch (IOException e) {
LOGGER.log(Level.WARNING, "Could not deserialize preKey from bytes.", e);
}
}
return null;
}
@Override
public void storeOmemoPreKey(OmemoDevice userDevice, int preKeyId, T_PreKey t_preKey) {
File preKeyPath = hierarchy.getPreKeyPath(userDevice, preKeyId);
writeBytes(preKeyPath, keyUtil().preKeyToBytes(t_preKey));
}
@Override
public void removeOmemoPreKey(OmemoDevice userDevice, int preKeyId) {
File preKeyPath = hierarchy.getPreKeyPath(userDevice, preKeyId);
if (!preKeyPath.delete()) {
LOGGER.log(Level.WARNING, "Deleting OMEMO preKey " + preKeyPath.getAbsolutePath() + " failed.");
}
}
@Override
public TreeMap<Integer, T_PreKey> loadOmemoPreKeys(OmemoDevice userDevice) {
File preKeyDirectory = hierarchy.getPreKeysDirectory(userDevice);
TreeMap<Integer, T_PreKey> preKeys = new TreeMap<>();
if (preKeyDirectory == null) {
return preKeys;
}
File[] keys = preKeyDirectory.listFiles();
for (File f : keys != null ? keys : new File[0]) {
byte[] bytes = readBytes(f);
if (bytes != null) {
try {
T_PreKey p = keyUtil().preKeyFromBytes(bytes);
preKeys.put(Integer.parseInt(f.getName()), p);
} catch (IOException e) {
LOGGER.log(Level.WARNING, "Could not deserialize preKey from bytes.", e);
}
}
}
return preKeys;
}
@Override
public T_SigPreKey loadOmemoSignedPreKey(OmemoDevice userDevice, int signedPreKeyId) {
File signedPreKeyPath = new File(hierarchy.getSignedPreKeysDirectory(userDevice), Integer.toString(signedPreKeyId));
byte[] bytes = readBytes(signedPreKeyPath);
if (bytes != null) {
try {
return keyUtil().signedPreKeyFromBytes(bytes);
} catch (IOException e) {
LOGGER.log(Level.WARNING, "Could not deserialize signed preKey from bytes.", e);
}
}
return null;
}
@Override
public TreeMap<Integer, T_SigPreKey> loadOmemoSignedPreKeys(OmemoDevice userDevice) {
File signedPreKeysDirectory = hierarchy.getSignedPreKeysDirectory(userDevice);
TreeMap<Integer, T_SigPreKey> signedPreKeys = new TreeMap<>();
if (signedPreKeysDirectory == null) {
return signedPreKeys;
}
File[] keys = signedPreKeysDirectory.listFiles();
for (File f : keys != null ? keys : new File[0]) {
byte[] bytes = readBytes(f);
if (bytes != null) {
try {
T_SigPreKey p = keyUtil().signedPreKeyFromBytes(bytes);
signedPreKeys.put(Integer.parseInt(f.getName()), p);
} catch (IOException e) {
LOGGER.log(Level.WARNING, "Could not deserialize signed preKey.", e);
}
}
}
return signedPreKeys;
}
@Override
public void storeOmemoSignedPreKey(OmemoDevice userDevice,
int signedPreKeyId,
T_SigPreKey signedPreKey) {
File signedPreKeyPath = new File(hierarchy.getSignedPreKeysDirectory(userDevice), Integer.toString(signedPreKeyId));
writeBytes(signedPreKeyPath, keyUtil().signedPreKeyToBytes(signedPreKey));
}
@Override
public void removeOmemoSignedPreKey(OmemoDevice userDevice, int signedPreKeyId) {
File signedPreKeyPath = new File(hierarchy.getSignedPreKeysDirectory(userDevice), Integer.toString(signedPreKeyId));
if (!signedPreKeyPath.delete()) {
LOGGER.log(Level.WARNING, "Deleting signed OMEMO preKey " + signedPreKeyPath.getAbsolutePath() + " failed.");
}
}
@Override
public T_Sess loadRawSession(OmemoDevice userDevice, OmemoDevice contactsDevice) {
File sessionPath = hierarchy.getContactsSessionPath(userDevice, contactsDevice);
byte[] bytes = readBytes(sessionPath);
if (bytes != null) {
try {
return keyUtil().rawSessionFromBytes(bytes);
} catch (IOException e) {
LOGGER.log(Level.WARNING, "Could not deserialize raw session.", e);
}
}
return null;
}
@Override
public HashMap<Integer, T_Sess> loadAllRawSessionsOf(OmemoDevice userDevice, BareJid contact) {
File contactsDirectory = hierarchy.getContactsDir(userDevice, contact);
HashMap<Integer, T_Sess> 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(userDevice, device);
byte[] bytes = readBytes(session);
if (bytes != null) {
try {
T_Sess s = keyUtil().rawSessionFromBytes(bytes);
sessions.put(id, s);
} catch (IOException e) {
LOGGER.log(Level.WARNING, "Could not deserialize raw session.", e);
}
}
}
return sessions;
}
@Override
public void storeRawSession(OmemoDevice userDevice, OmemoDevice contactsDevice, T_Sess session) {
File sessionPath = hierarchy.getContactsSessionPath(userDevice, contactsDevice);
writeBytes(sessionPath, keyUtil().rawSessionToBytes(session));
}
@Override
public void removeRawSession(OmemoDevice userDevice, OmemoDevice contactsDevice) {
File sessionPath = hierarchy.getContactsSessionPath(userDevice, contactsDevice);
if (!sessionPath.delete()) {
LOGGER.log(Level.WARNING, "Deleting raw OMEMO session " + sessionPath.getAbsolutePath() + " failed.");
}
}
@Override
public void removeAllRawSessionsOf(OmemoDevice userDevice, BareJid contact) {
File contactsDirectory = hierarchy.getContactsDir(userDevice, contact);
String[] devices = contactsDirectory.list();
for (String deviceId : devices != null ? devices : new String[0]) {
int id = Integer.parseInt(deviceId);
OmemoDevice device = new OmemoDevice(contact, id);
File session = hierarchy.getContactsSessionPath(userDevice, device);
if (!session.delete()) {
LOGGER.log(Level.WARNING, "Deleting raw OMEMO session " + session.getAbsolutePath() + "failed.");
}
}
}
@Override
public boolean containsRawSession(OmemoDevice userDevice, OmemoDevice contactsDevice) {
File session = hierarchy.getContactsSessionPath(userDevice, contactsDevice);
return session.exists();
}
@Override
public OmemoCachedDeviceList loadCachedDeviceList(OmemoDevice userDevice, BareJid contact) {
OmemoCachedDeviceList cachedDeviceList = new OmemoCachedDeviceList();
if (contact == null) {
throw new IllegalArgumentException("Contact can not be null.");
}
// active
File activeDevicesPath = hierarchy.getContactsActiveDevicesPath(userDevice, contact);
Set<Integer> active = readIntegers(activeDevicesPath);
if (active != null) {
cachedDeviceList.getActiveDevices().addAll(active);
}
// inactive
File inactiveDevicesPath = hierarchy.getContactsInactiveDevicesPath(userDevice, contact);
Set<Integer> inactive = readIntegers(inactiveDevicesPath);
if (inactive != null) {
cachedDeviceList.getInactiveDevices().addAll(inactive);
}
return cachedDeviceList;
}
@Override
public void storeCachedDeviceList(OmemoDevice userDevice,
BareJid contact,
OmemoCachedDeviceList contactsDeviceList) {
if (contact == null) {
return;
}
File activeDevices = hierarchy.getContactsActiveDevicesPath(userDevice, contact);
writeIntegers(activeDevices, contactsDeviceList.getActiveDevices());
File inactiveDevices = hierarchy.getContactsInactiveDevicesPath(userDevice, contact);
writeIntegers(inactiveDevices, contactsDeviceList.getInactiveDevices());
}
@Override
public void purgeOwnDeviceKeys(OmemoDevice userDevice) {
File deviceDirectory = hierarchy.getUserDeviceDirectory(userDevice);
deleteDirectory(deviceDirectory);
}
private static void writeLong(File target, long i) {
if (target == null) {
LOGGER.log(Level.WARNING, "Could not write long to null-path.");
return;
}
try {
FileHierarchy.createFile(target);
} catch (IOException e) {
LOGGER.log(Level.SEVERE, "Could not create file.", e);
return;
}
DataOutputStream out = null;
try {
out = new DataOutputStream(new FileOutputStream(target));
out.writeLong(i);
} catch (IOException e) {
LOGGER.log(Level.SEVERE, "Could not write longs to file.", e);
} finally {
if (out != null) {
try {
out.close();
} catch (IOException e) {
LOGGER.log(Level.SEVERE, "Could not close OutputStream.", e);
}
}
}
}
private static Long readLong(File target) {
if (target == null) {
LOGGER.log(Level.WARNING, "Could not read long from null-path.");
return null;
}
Long l;
DataInputStream in = null;
try {
in = new DataInputStream(new FileInputStream(target));
l = in.readLong();
} catch (FileNotFoundException e) {
l = null;
} catch (IOException e) {
LOGGER.log(Level.SEVERE, "Could not read long from file.", e);
return null;
} finally {
if (in != null) {
try {
in.close();
} catch (IOException e) {
LOGGER.log(Level.SEVERE, "Could not close InputStream.", e);
}
}
}
return l;
}
private static void writeBytes(File target, byte[] bytes) {
if (target == null) {
LOGGER.log(Level.WARNING, "Could not write bytes to null-path.");
return;
}
// Create file
try {
FileHierarchy.createFile(target);
} catch (IOException e) {
LOGGER.log(Level.SEVERE, "Could not create file.", e);
return;
}
DataOutputStream out = null;
try {
out = new DataOutputStream(new FileOutputStream(target));
out.write(bytes);
} catch (IOException e) {
LOGGER.log(Level.SEVERE, "Could not write bytes to file.", e);
} finally {
if (out != null) {
try {
out.close();
} catch (IOException e) {
LOGGER.log(Level.SEVERE, "Could not close OutputStream.", e);
}
}
}
}
private static byte[] readBytes(File target) {
if (target == null) {
LOGGER.log(Level.WARNING, "Could not read bytes from null-path.");
return null;
}
byte[] b = null;
DataInputStream in = null;
try {
in = new DataInputStream(new FileInputStream(target));
b = new byte[(int) target.length()];
in.read(b);
} catch (FileNotFoundException e) {
b = null;
} catch (IOException e) {
LOGGER.log(Level.SEVERE, "Could not read bytes from file.", e);
} finally {
if (in != null) {
try {
in.close();
} catch (IOException e) {
LOGGER.log(Level.SEVERE, "Could not close InputStream.", e);
}
}
}
return b;
}
private static void writeIntegers(File target, Set<Integer> integers) {
if (target == null) {
LOGGER.log(Level.WARNING, "Could not write integers to null-path.");
return;
}
DataOutputStream out = null;
try {
out = new DataOutputStream(new FileOutputStream(target));
for (int i : integers) {
out.writeInt(i);
}
} catch (IOException e) {
LOGGER.log(Level.SEVERE, "Could not write integers to file.", e);
} finally {
if (out != null) {
try {
out.close();
} catch (IOException e) {
LOGGER.log(Level.SEVERE, "Could not close OutputStream.", e);
}
}
}
}
private static Set<Integer> readIntegers(File target) {
if (target == null) {
LOGGER.log(Level.WARNING, "Could not read integers from null-path.");
return null;
}
HashSet<Integer> integers = new HashSet<>();
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 (FileNotFoundException e) {
integers = null;
} catch (IOException e) {
LOGGER.log(Level.SEVERE, "Could not read integers.", e);
} finally {
if (in != null) {
try {
in.close();
} catch (IOException e) {
LOGGER.log(Level.SEVERE, "Could not close InputStream.", e);
}
}
}
return integers;
}
/**
* One day... *sheds a tear*
* TODO Use methods below once Smack's minimum Android API level is 19 or higher
*/
/*
private static void writeLong(File target, long i)
throws IOException
{
if (target == null) {
throw new IOException("Could not write long to null-path.");
}
FileHierarchy.createFile(target);
try (DataOutputStream out = new DataOutputStream(new FileOutputStream(target))) {
out.writeLong(i);
}
}
private static Long readLong(File target)
throws IOException
{
if (target == null) {
throw new IOException("Could not read long from null-path.");
}
if (!target.exists() || !target.isFile()) {
return null;
}
try (DataInputStream in = new DataInputStream(new FileInputStream(target))) {
return in.readLong();
}
}
private static 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);
try (DataOutputStream out = new DataOutputStream(new FileOutputStream(target))) {
out.write(bytes);
}
}
private static byte[] readBytes(File target)
throws IOException
{
if (target == null) {
throw new IOException("Could not read bytes from null-path.");
}
if (!target.exists() || !target.isFile()) {
return null;
}
byte[] b = new byte[(int) target.length()];
try (DataInputStream in = new DataInputStream(new FileInputStream(target))) {
in.read(b);
}
return b;
}
private static void writeIntegers(File target, Set<Integer> integers)
throws IOException
{
if (target == null) {
throw new IOException("Could not write integers to null-path.");
}
FileHierarchy.createFile(target);
try (DataOutputStream out = new DataOutputStream(new FileOutputStream(target))) {
for (int i : integers) {
out.writeInt(i);
}
}
}
private static Set<Integer> readIntegers(File target)
throws IOException
{
if (target == null) {
throw new IOException("Could not write integers to null-path.");
}
if (!target.exists() || !target.isFile()) {
return null;
}
HashSet<Integer> integers = new HashSet<>();
try (DataInputStream in = new DataInputStream(new FileInputStream(target))) {
while (true) {
try {
integers.add(in.readInt());
} catch (EOFException e) {
break;
}
}
}
return integers;
}
*/
/**
* Delete a directory with all subdirectories.
* @param root
*/
public static void deleteDirectory(File root) {
File[] currList;
Stack<File> 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 FileBasedOmemoStore.
* The directory looks as follows:
*
* OMEMO_Store/
* 'romeo@montague.lit'/ //Our bareJid
* ...
* 'juliet@capulet.lit'/ //Our other bareJid
* '13371234'/ //deviceId
* identityKeyPair //Our identityKeyPair
* 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 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_DEVICEID_PUBLICATION_DATE = "lastDeviceIdPublicationDate";
static final String SIGNED_PRE_KEYS = "signedPreKeys";
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";
File basePath;
FileHierarchy(File basePath) {
this.basePath = basePath;
basePath.mkdirs();
}
File getStoreDirectory() {
return createDirectory(basePath, STORE);
}
File getUserDirectory(OmemoDevice userDevice) {
return getUserDirectory(userDevice.getJid());
}
File getUserDirectory(BareJid bareJid) {
return createDirectory(getStoreDirectory(), bareJid.toString());
}
File getUserDeviceDirectory(OmemoDevice userDevice) {
return createDirectory(getUserDirectory(userDevice.getJid()),
Integer.toString(userDevice.getDeviceId()));
}
File getContactsDir(OmemoDevice userDevice) {
return createDirectory(getUserDeviceDirectory(userDevice), CONTACTS);
}
File getContactsDir(OmemoDevice userDevice, BareJid contact) {
return createDirectory(getContactsDir(userDevice), contact.toString());
}
File getContactsDir(OmemoDevice userDevice, OmemoDevice contactsDevice) {
return createDirectory(getContactsDir(userDevice, contactsDevice.getJid()),
Integer.toString(contactsDevice.getDeviceId()));
}
File getIdentityKeyPairPath(OmemoDevice userDevice) {
return new File(getUserDeviceDirectory(userDevice), IDENTITY_KEY_PAIR);
}
File getPreKeysDirectory(OmemoDevice userDevice) {
return createDirectory(getUserDeviceDirectory(userDevice), PRE_KEYS);
}
File getPreKeyPath(OmemoDevice userDevice, int preKeyId) {
return new File(getPreKeysDirectory(userDevice), Integer.toString(preKeyId));
}
File getLastMessageReceivedDatePath(OmemoDevice userDevice, OmemoDevice device) {
return new File(getContactsDir(userDevice, device), LAST_MESSAGE_RECEVIED_DATE);
}
File getLastDeviceIdPublicationDatePath(OmemoDevice userDevice, OmemoDevice device) {
return new File(getContactsDir(userDevice, device), LAST_DEVICEID_PUBLICATION_DATE);
}
File getSignedPreKeysDirectory(OmemoDevice userDevice) {
return createDirectory(getUserDeviceDirectory(userDevice), SIGNED_PRE_KEYS);
}
File getLastSignedPreKeyRenewal(OmemoDevice userDevice) {
return new File(getUserDeviceDirectory(userDevice), LAST_SIGNED_PRE_KEY_RENEWAL);
}
File getContactsIdentityKeyPath(OmemoDevice userDevice, OmemoDevice contactsDevice) {
return new File(getContactsDir(userDevice, contactsDevice), IDENTITY_KEY);
}
File getContactsSessionPath(OmemoDevice userDevice, OmemoDevice contactsDevice) {
return new File(getContactsDir(userDevice, contactsDevice), SESSION);
}
File getContactsActiveDevicesPath(OmemoDevice userDevice, BareJid contact) {
return new File(getContactsDir(userDevice, contact), DEVICE_LIST_ACTIVE);
}
File getContactsInactiveDevicesPath(OmemoDevice userDevice, BareJid contact) {
return new File(getContactsDir(userDevice, contact), DEVICE_LIST_INAVTIVE);
}
private static File createFile(File f) throws IOException {
File p = f.getParentFile();
createDirectory(p);
f.createNewFile();
return f;
}
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;
}
}
}