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 }