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}