001    package edu.harvard.deas.hyperenc;
002    
003    import java.io.File;
004    import java.security.MessageDigest;
005    import java.util.ArrayList;
006    import java.util.LinkedList;
007    import java.util.List;
008    
009    
010    
011    /**
012     * Stores unreconciled pages and corresponding hash values, reconciled pages,
013     * system blocks, and encryption blocks for a single contact. This storage is
014     * backed by a {@link PersistentMap}, so insertions and removals of blocks/pages
015     * in this storage are synchronously written to disk, and can persist across
016     * instances of the application.
017     * <p>
018     * <b>Thread safety:</b> This implementation is unconditionally thread-safe.
019     */
020    public class DBHyperStorage extends HyperStorage {
021      /** Indicates if this storage is the master of the communication path */
022      private Direction direction;
023    
024      /**
025       * Collection of system blocks.
026       */
027      private PersistentMap<Integer,byte[][]> sysblocks;
028      
029      /**
030       * Collection of encryption blocks. Must synchronize on this map when
031       * removing; see <code>remEncBlockList</code>.
032       */
033      // XXX would be better to make an EncryptionBlockMap to enforce this
034      private PersistentMap<Integer,byte[]> encblocks;
035      
036      /**
037       * Collection of unreconciled pages.
038       */
039      private PersistentMap<Integer,byte[]> upages;
040      
041      /**
042       * Hashes of unreconciled pages; keys correspond to those of <code>upages</code>.
043       */
044      private PersistentMap<Integer,byte[]> uhashes;
045      
046      /**
047       * Collection of reconciled pages.
048       */
049      private PersistentMap<Integer,byte[]> rpages;
050    
051      /** Provider of our random data.  Should be either WebRandomSource or VSATRandomSource */
052      private RandomSource source;
053      
054      /** Used to compute hashes of the unreconciled pages */
055      private MessageDigest digest;
056    
057      /** Used to shuffle page. */
058      private PageShuffler shuffle;
059    
060      /** The partner for which this Storage stores things. */
061      private Contact contact;
062    
063      /**
064       * Constructs a new DBHyperStorage. If the given database environment already
065       * contains blocks/pages for <code>contact</code>, they are loaded,
066       * restoring the state of this object to the state of the last DBHyperStorage
067       * that used the database. Otherwise, the new DBHyperStorage is empty.
068       * 
069       * @param contact
070       *        The partner for whom this storage stores things
071       * @param direction
072       *        Indicates if this storage is the master of the communication path
073       * @param rs
074       *        The random source used to create pages for this path of
075       *        communication
076       * @param md
077       *        The hash function used as a component of computing the hash of a
078       *        page.
079       * @param ps
080       *        The page shuffler used to shuffle pages fetched for this storage.
081       * @param envPath
082       *        The path on disk of the database environment underlying the database
083       *        backing this map. This File is passed directly to the constructor
084       *        for PersistentMap.
085       */
086      public DBHyperStorage(Contact contact, Direction direction, RandomSource
087                      rs, MessageDigest md, PageShuffler ps, File envPath) {
088        String mastery = (direction == Direction.MASTER) ? ".Master." : ".Slave.";
089        
090        this.direction = direction;
091        this.contact = contact;
092    
093        sysblocks = PersistentMap.getInstance(contact.getEmail().toString() + mastery
094            + "sysblocksdb", Integer.class, byte[][].class, envPath);
095        encblocks = PersistentMap.getInstance(contact.getEmail().toString() + mastery
096            + "encblocksdb", Integer.class, byte[].class, envPath);
097        upages = PersistentMap.getInstance(contact.getEmail().toString() + mastery
098            + "upagesdb", Integer.class, byte[].class, envPath);
099        rpages = PersistentMap.getInstance(contact.getEmail().toString() + mastery
100            + "rpagesdb", Integer.class, byte[].class, envPath);
101        uhashes = PersistentMap.getInstance(contact.getEmail().toString() + mastery
102            + "uhashesdb", Integer.class, byte[].class, envPath);
103    
104        source = rs;
105        digest = md;
106        shuffle = ps;
107      }
108    
109      /**
110       * Equivalent to calling
111       * <code>DBHyperStorage(contact, direction, rs, md, ps, null)</code>.
112       */
113      public DBHyperStorage(Contact contact, Direction direction, RandomSource
114          rs, MessageDigest md, PageShuffler ps) {
115        this(contact, direction, rs, md, ps, null);
116      }
117    
118      // INSERTION INTO STORAGE
119      
120      @Override
121      public int addSysBlock(byte [][] blocks) {
122        synchronized (sysblocks) {
123          return intAppend(sysblocks, blocks);
124        }
125      }
126    
127      @Override
128      public int addEncBlock(byte [] block){
129        synchronized (encblocks) {
130          return intAppend(encblocks, block);
131        }
132      }
133    
134      @Override
135      public void addUPage(int id, byte [] page, byte [] hash) {
136        upages.put(id, page);
137        uhashes.put(id, hash);
138      }
139    
140      @Override
141      public void addRPage(byte [] page){
142        intAppend(rpages, page);
143      }
144    
145      // QUERY ABOUT STORAGE
146    
147      @Override
148      public List<Integer> getSysBlockList() {
149        return new LinkedList<Integer>(sysblocks.keySet());
150      }
151      
152      @Override
153      public List<Integer> getEncBlockList() {
154        return new LinkedList<Integer>(encblocks.keySet());
155      }
156      
157      @Override
158      public List<Integer> getUPageList() {
159        return new ArrayList<Integer>(upages.keySet());
160      }
161    
162      // RETRIEVAL FROM STORAGE
163    
164      @Override
165      public byte [][] getSysBlock(int id) {
166        return sysblocks.get(id);
167      }
168      
169      @Override
170      public byte [] getEncBlock(int id) {
171        return encblocks.get(id);
172      }
173      
174      @Override
175      public byte [] getUPage(int id) {
176        return upages.get(id);
177      }
178      
179      @Override
180      public byte [] getUHash(int id) {
181        return uhashes.get(id);
182      }
183      
184      // REMOVAL FROM STORAGE
185      
186      @Override
187      public byte[][] remSysBlock(int id) {
188        return sysblocks.remove(id);
189      }
190      
191      @Override
192      public byte[] remEncBlock(int id) {
193        synchronized (encblocks) {
194          return encblocks.remove(id);
195        }
196      }
197      
198      @Override
199      public List<byte[]> remEncBlockList(List<Integer> idlist)
200          throws BlockMissingException {
201        synchronized (encblocks) {
202          List<Integer> missinglist = new ArrayList<Integer>();
203          for (int id : idlist) {
204            if (!encblocks.containsKey(id))
205              missinglist.add(id);
206          }
207          if (!missinglist.isEmpty())
208            throw new BlockMissingException(missinglist, missinglist.size()
209                + " required encryption blocks were missing");
210          
211          // All IDs are valid; proceed to remove the blocks
212          List<byte[]> blocklist = new ArrayList<byte[]>();
213          for (int id : idlist) {
214            byte[] b = encblocks.remove(id);
215            assert(b != null);
216            blocklist.add(b);
217          }
218          return blocklist;
219        }
220      }
221      
222      @Override
223      public byte[] remUPage(int id) {
224        uhashes.remove(id);
225        return upages.remove(id);
226      }
227      
228      @Override
229      public byte[] remRPage() {
230        return rpages.removeFirst();
231      }
232    
233      
234      // Specialized tools for accessing data
235      
236      @Override
237      public int numSysBlocks() {
238        return sysblocks.size();
239      }
240      
241      @Override
242      public int numEncBlocks() {
243        return encblocks.size();
244      }
245      
246      @Override
247      public int numUPages() {
248        return upages.size();
249      }
250      
251      @Override
252      public int numRPages() {
253        return rpages.size();
254      }
255    
256      
257      // PROPERTIES
258      @Override
259      public Contact getContact() {
260        return contact;
261      }
262      
263      @Override
264      public RandomSource getSource() {
265        return source;
266      }
267      
268      @Override
269      public MessageDigest getDigest() {
270        return digest;
271      }
272      
273      @Override
274      public PageShuffler getShuffler() {
275        return shuffle;
276      }
277      
278      @Override
279      public Direction getDirection() {
280        return direction;
281      }
282    
283      /**
284       * Put <code>value</code> into <code>map</code>, using an
285       * automatically-generated key. The key for this value is computed as follows:
286       * <ul>
287       * <li>If this map is not empty, the key is exactly one greater than the
288       * largest key in the map (i.e., <code>this.lastKey() + 1</code>).</li>
289       * <li>If this map is empty, the key is 0.</li>
290       * </ul>
291       * The value is then inserted in the same manner as
292       * {@link PersistentMap#put(Object, Object)}.
293       * <p>
294       * <i>Note on thread-safety:</i> No new elements may be inserted into the map
295       * while this method is running. In particular, if a new key were inserted
296       * that is equal to the key computed by this method, one of the new values
297       * would be replaced by the other, resulting in potential data loss.
298       * 
299       * @param value
300       *        The value to insert.
301       * @return The key with which <code>value</code> is now associated.
302       */
303      private <V> int intAppend(PersistentMap<Integer,V> map, V value) {
304        int nextkey;
305        if (map.isEmpty()) {
306          nextkey = 0;
307        } else {
308          nextkey = map.lastKey() + 1;
309        }
310    
311        map.put(nextkey, value);
312        
313        return nextkey;
314      }
315    }