001 package edu.harvard.deas.hyperenc.vsat; 002 003 import java.io.Serializable; 004 import java.util.ArrayList; 005 import java.util.Collections; 006 import java.util.Date; 007 import java.util.HashMap; 008 import java.util.LinkedList; 009 import java.util.List; 010 import java.util.Map; 011 import java.util.Queue; 012 import java.util.Random; 013 014 import edu.harvard.deas.hyperenc.util.Pair; 015 016 /** 017 * A PageDatabase that implements lookup as described in Section II-B1 of M. 018 * Rabin's 2005 paper ("Provably Unbreakable Hyper-Encryption In the Limited 019 * Access Model") 020 * <p> 021 * When first generated, pages are not associated with a key. When looking up a 022 * page, KeyedPageDatabase checks for any pages already associated with the 023 * given key. If there is one, and the page is not stale, it returns and then 024 * destroys it. Otherwise, it assigns the key to the next available page. 025 * <p> 026 * If the database becomes too large (as a result of an abundance of request 027 * keys only being used once), the database discards random key-page pairs to 028 * free up space. 029 * <p> 030 * A KeyedPageDatabase periodically checks if its pages are stale. A page is 031 * considered stale if it has been requested once, and some amount of time (the 032 * <i>stale page time</i>) has passed before a second request is made. If a page 033 * is found to be stale, it is discarded (destroyed). For performance reasons, 034 * stale pages might not be detected/destroyed immediately, but it is guaranteed 035 * that a stale page will never be returned to a client. 036 */ 037 public class KeyedPageDatabase 038 extends Thread 039 implements Serializable, PageDatabase 040 { 041 private static final long serialVersionUID = 1L; 042 043 /** Controls debugging output. */ 044 private static final boolean DEBUG = false; 045 046 /** 047 * The <i>stale page time</i> is the number of milliseconds after which a 048 * page that has been requested once is considered stale and discarded. 049 * STALE_PAGE_TIME is the stale page time for this PageDatabase; its 050 * default value is DEFAULT_STALE_PAGE_TIME. 051 */ 052 public static final long DEFAULT_STALE_PAGE_TIME = 1000 * 60 * 60 * 24 * 3; // three days 053 private long STALE_PAGE_TIME; 054 055 /** Maximum number of PageCreators to run simultaneously. */ 056 private static final int MAX_CONCURRENCY = 5; 057 058 /** 059 * Maximum allowable number of pages that have been requested exactly once. 060 * (These pages are stored in a map, with associated request keys.) 061 */ 062 private static final int MAP_MAX_SIZE = 1000; 063 064 /** 065 * Maximum number of "fresh" (never-served) pages to keep. (These pages are 066 * stored in a stack, with pages being removed from the top as needed.) 067 */ 068 private static final int STACK_MAX_SIZE = 100; 069 070 /** 071 * Minimum number of "fresh" pages to have on hand. When the page stack falls 072 * below this size, new pages are generated. 073 */ 074 private static final int STACK_MIN_SIZE = 50; 075 076 /** 077 * The collection of pages that have been requested exactly once. Each key is 078 * a request key, and the associated value is the page served in response to 079 * that key. All manipulation of this map must be done while synchronized on 080 * pageDB. 081 */ 082 private Map<Integer,Pair<VSatPage,Date>> pageDB; 083 084 /** 085 * The collection of pages that have never been requested. All manipulation of 086 * this queue must be done while synchronized on pageDB. 087 */ 088 private Queue<VSatPage> backs; 089 090 /** 091 * The most recent time when we checked pageDB for stale pages. 092 */ 093 private Date lastStaleCheckTime; 094 095 /** The number of PageCreators running at the moment. */ 096 private Integer running; 097 private Integer runningMutex; 098 099 /** 100 * Random number generator for use when evicting a random page from the 101 * database. 102 */ 103 private Random fumbles; 104 105 /** 106 * Creates a new KeyedPageDatabase. Generates MAX_NUM_PAGES and inserts them 107 * into the database. 108 * @param stalePageTime 109 * The stale page time for this database. 110 * @see #DEFAULT_STALE_PAGE_TIME 111 */ 112 public KeyedPageDatabase(long stalePageTime) 113 { 114 this.STALE_PAGE_TIME = stalePageTime; 115 116 pageDB = Collections.synchronizedMap(new HashMap<Integer,Pair<VSatPage,Date>>()); 117 backs = new LinkedList<VSatPage>(); 118 running = new Integer(0); 119 runningMutex = new Integer(0); 120 121 lastStaleCheckTime = new Date(); 122 123 // this doesn't have to be secure 124 fumbles = new Random(); 125 126 // generate our initial complement of pages 127 generateNewPages(); 128 129 checkRep(); 130 } 131 132 public KeyedPageDatabase() { 133 this(DEFAULT_STALE_PAGE_TIME); 134 } 135 136 /** 137 * Enforce class invariants. 138 */ 139 private void checkRep() { 140 if (STALE_PAGE_TIME <= 0) { 141 throw new RuntimeException("The STALE_PAGE_TIME is non-positive."); 142 } 143 } 144 145 /** 146 * @return The stale page time, in seconds. 147 */ 148 public long getStalePageTime() { 149 checkRep(); 150 return STALE_PAGE_TIME; 151 } 152 153 /** 154 * Evicts a random page from the database. May only be called while 155 * synchronized on pageDB. 156 */ 157 private void fumble() { 158 Object[] keys = pageDB.keySet().toArray(); 159 int i = fumbles.nextInt(keys.length); 160 pageDB.remove(keys[i]); 161 if (DEBUG) System.out.println("Database too large; fumbling page "+i+"!"); 162 checkRep(); 163 } 164 165 /** 166 * Goes through pageDB and throws out all the stale pages. MUST NOT be called 167 * while synchronized on pageDB. 168 */ 169 private void sweepStalePages() { 170 if (DEBUG) System.out.println("Sweeping stale pages"); 171 172 synchronized (pageDB) { 173 // First, find all the stale pages for later removal (we can't remove 174 // them now because we're iterating over them!) 175 List<Integer> staleKeys = new ArrayList<Integer>(); 176 for (int key : pageDB.keySet()) { 177 Pair<VSatPage,Date> p = pageDB.get(key); 178 Date now = new Date(); 179 if (p.second().getTime() + STALE_PAGE_TIME < now.getTime()) { 180 staleKeys.add(key); 181 } 182 } 183 184 // Now, remove them 185 for (int staleKey : staleKeys) { 186 if (DEBUG) System.out.println("Removing stale page " + staleKey); 187 Pair<VSatPage,Date> p = pageDB.remove(staleKey); 188 assert(p != null); 189 } 190 } 191 192 lastStaleCheckTime = new Date(); 193 } 194 195 /** 196 * Returns the maximum number of pages that can be stored in the database. 197 * This counts only pages in the map (that is, the maximum number of 198 * requested-once pages that this database will keep track of). 199 * 200 * @return maximum number of pages that can be stored in the map 201 */ 202 @Override 203 public int getMaxPages() { 204 return MAP_MAX_SIZE; 205 } 206 207 /** 208 * Returns the number of pages currently stored in this database that have 209 * been requested exactly once. That is, returns the number of pages in the 210 * map. 211 * 212 * @return number of pages in the map 213 */ 214 @Override 215 public int getNumPages() { 216 return pageDB.size(); 217 } 218 219 /** 220 * Returns the number of pages currently stored in this database that have 221 * never been requested. That is, returns the number of pages in the queue. 222 */ 223 public int getQueueSize() { 224 return backs.size(); 225 } 226 227 /** Get a page from the database. 228 @param key The key we are trying to match. 229 @return The page whose key matched the given key, or if none exists that is not stale, 230 then a new page that has never been served. Returns <code>null</code> 231 if the database is empty. 232 */ 233 public byte[] getPage(int key) 234 { 235 checkRep(); 236 237 // XXX is returning null really the right course of action? Maybe an 238 // exception would be more appropriate 239 VSatPage thisPage = null; 240 241 boolean makeNew=false; 242 243 //get closest page 244 synchronized (pageDB) { 245 // if there's already a page with the given key, check if it's stale 246 // if so, discard it; if not, return it 247 if (pageDB.containsKey(key)) { 248 Pair<VSatPage,Date> thisPair = pageDB.remove(key); 249 Date now = new Date(); 250 if (thisPair.second().getTime() + STALE_PAGE_TIME > now.getTime()) { 251 thisPage = thisPair.first(); 252 if (DEBUG) System.out.println("Serving page "+key+" from the hash."); 253 } else { 254 if (DEBUG) System.out.println("Page "+key+" from the hash is stale; discarding. (1st req: " + thisPair.second().getTime() + ", now: " + now.getTime() + ", stale after " + STALE_PAGE_TIME + "ms)"); 255 } 256 } 257 258 if (thisPage == null) { 259 if (!backs.isEmpty()) { 260 thisPage = backs.remove(); 261 pageDB.put(key, new Pair<VSatPage,Date>(thisPage, new Date())); 262 if (DEBUG) System.out.println("Serving page "+key+" from the stack."); 263 264 // database size management 265 if (backs.size() < STACK_MIN_SIZE) 266 makeNew = true; 267 if (pageDB.size() > MAP_MAX_SIZE) 268 fumble(); 269 } else { 270 makeNew = true; 271 if (DEBUG) System.out.println("Underflow on page "+key+"!"); 272 } 273 } 274 } 275 276 // If we haven't checked for stale pages recently, create a thread 277 // that will do it once this method returns 278 Date now = new Date(); 279 if (lastStaleCheckTime.getTime() + STALE_PAGE_TIME < now.getTime()) 280 sweepStalePages(); 281 282 if (makeNew) generateNewPages(); 283 284 checkRep(); 285 286 return thisPage.getData(); 287 } 288 289 /** Generate a new page and put it into the database */ 290 private void generateNewPages() 291 { 292 // Spawn a thread to collect the page 293 PageCreator pc = new PageCreator(); 294 295 synchronized (runningMutex) { 296 if (running.intValue() < MAX_CONCURRENCY) { 297 running = new Integer(1 + running.intValue()); 298 pc.start(); 299 } 300 } 301 } 302 303 private class PageCreator extends Thread { 304 public void run() { 305 boolean keepgoing=true; 306 307 //call a new TrueRandomSource to get file data 308 TrueRandomSource myRS; 309 if (DevRandomSource.randomDevice() != null) { 310 myRS = new DevRandomSource(); 311 if (DEBUG) 312 System.out.println("Node: getting randomness from "+ 313 DevRandomSource.randomDevice()); 314 } else { 315 if (DEBUG) 316 System.out.println("Node: getting randomness from FileRandomSource"); 317 myRS = new FileRandomSource(); 318 } 319 320 VSatPage newPage = null; 321 byte[] data; 322 323 324 while (keepgoing) { 325 data=null; 326 do { 327 data = myRS.genRandomness(); 328 } while (data == null); 329 330 //create a new VSatPage with this data 331 newPage = new VSatPage(data); 332 333 synchronized (pageDB) { 334 backs.add(newPage); 335 if (DEBUG) System.out.println("Added a page"); 336 if (backs.size() >= STACK_MAX_SIZE) 337 keepgoing=false; 338 } 339 } 340 341 synchronized (runningMutex) { 342 running = new Integer(running.intValue() - 1); 343 } 344 } 345 } 346 } 347