001 package edu.harvard.deas.hyperenc.vsat; 002 003 import java.io.DataInputStream; 004 import java.io.DataOutputStream; 005 import java.io.File; 006 import java.io.FileInputStream; 007 import java.io.FileOutputStream; 008 import java.io.IOException; 009 import java.io.ObjectInputStream; 010 import java.io.ObjectOutputStream; 011 import java.net.InetSocketAddress; 012 import java.net.ServerSocket; 013 import java.net.Socket; 014 import java.net.SocketAddress; 015 016 import org.apache.log4j.Logger; 017 018 /** 019 * The PageServerNode represents a Page Server Node in the hyper-encryption 020 * system. It listens for page requests and responds to them. Each request 021 * consists of a 32-bit integer, the <i>page request key</i>. The PSN responds 022 * to a page request by sending back a 4096-byte page of random data; each page 023 * is served <i>at most twice</i>. 024 * <p> 025 * A PageServerNode is backed by a PageDatabase, which handles the details of 026 * page management. The PageServerNode itself receives requests and uses the 027 * contained request key to retrieve a page from the PageDatabase. It then sends 028 * this page back to the requester. 029 * <p> 030 * The request key is interpreted as a big-endian integer: the high byte comes 031 * first. 032 * 033 * @see PageDatabase 034 */ 035 public class PageServerNode extends Thread 036 { 037 private static final Logger logger = Logger.getLogger(PageServerNode.class); 038 039 /** 040 * Default file for writing out the page database 041 */ 042 private static final String DEFAULT_PAGEDB_FILE = "vsatpagedb.dat"; 043 044 /** 045 * Default number of connections to backlog. 046 */ 047 private static final int DEFAULT_BACKLOG = 11; 048 049 /** 050 * The port on which the PSN listens for connections/requests. 051 */ 052 public static final int DEFAULT_PSN_PORT = 48369; 053 054 /** 055 * Socket used to listen for incoming requests. 056 */ 057 private ServerSocket ss = null; 058 059 /** 060 * Stores the individual pages, tracks access counts, and generates new pages 061 */ 062 private PageDatabase pageDB; 063 064 /** 065 * Whether to shut down the node. 066 */ 067 private boolean shutdown = false; 068 069 /** 070 * keep track of the datebase's file name 071 */ 072 private String dbFile; //to keep track of the name 073 074 075 /** 076 * The port on which the PSN listens for connections. 077 */ 078 private final int PSN_PORT; 079 080 081 /** Creates a PSN and runs it until the process is killed. */ 082 public static void main(String args[]) { 083 System.out.println("Initializing PSN. Use ^C to shut down the PSN."); 084 085 try { 086 PageServerNode psn = new PageServerNode(); 087 psn.run(); 088 } catch (Exception e) { 089 e.printStackTrace(); 090 System.err.println("The PSN has experienced an unexpected error. " 091 + "Please report this error to the developers, including the " 092 + "stack trace above, so that we can find and fix the problem."); 093 System.exit(2); 094 } 095 } 096 097 098 /** 099 * Creates a new Page Server Node using the default port. 100 * Equivalent to PageServerNode(DEFAULT_PSN_PORT). 101 */ 102 public PageServerNode() 103 { 104 this(DEFAULT_PSN_PORT); 105 } 106 107 108 /** 109 * Creates a new Virtual Satellite node that listens for connections on 110 * <code>psn_port</code>. 111 * This does all of the network setup as well 112 * @param psn_port The port on which to listen for connections. 113 */ 114 public PageServerNode(int psn_port) 115 { 116 PSN_PORT = psn_port; 117 118 // TODO read in a config file 119 120 // if a page database already exists on disk, read it in 121 dbFile = PageServerNode.DEFAULT_PAGEDB_FILE; 122 File f = new File(dbFile); 123 124 if (f != null && f.exists() && f.isFile()) 125 { 126 logger.info("Trying to load PageDB"); 127 try 128 { 129 ObjectInputStream in = 130 new ObjectInputStream(new FileInputStream(dbFile)); 131 pageDB = (KeyedPageDatabase)in.readObject(); 132 logger.info("Successfully loaded page database from " 133 + f.getPath() + "."); 134 } catch (IOException ex) { 135 // if error opening file, abort and warn the user 136 logger.error("Error reading database file " + f.getPath() + ": " 137 + ex.getMessage()); 138 System.exit(3); 139 } catch (ClassNotFoundException ex) { 140 // if error opening file, abort and warn the user 141 logger.error("Invalid database file " + f.getPath() + ": " 142 + ex.getMessage()); 143 System.exit(3); 144 } 145 } else { 146 // if there is no existing database, create a new, empty one 147 // this will also create an initial set of pages! 148 logger.warn("No database found in " + f.getPath() 149 + ". Creating and populating new page database."); 150 pageDB = new KeyedPageDatabase(); 151 } 152 153 // Create cleanup thread and set it up to run at VM shutdown 154 Runtime.getRuntime().addShutdownHook(new PageServerNodeCleanupThread()); 155 } 156 157 158 /** 159 * Whether or not to shut down. 160 * @return Whether we have been told to shut down. 161 */ 162 public synchronized boolean isShutdown() 163 { 164 return shutdown; 165 } 166 167 /** 168 * Shut down the server now. 169 */ 170 public synchronized void shutdownNow() 171 { 172 shutdown = true; 173 174 try 175 { 176 logger.info("Writing page database to disk."); 177 ObjectOutputStream out = new ObjectOutputStream 178 (new FileOutputStream (dbFile)); 179 out.writeObject(pageDB); 180 } 181 catch(IOException ex) 182 { 183 logger.error("Could not write db to disk", ex); 184 } 185 } 186 187 /** 188 * The main thread of execution for the PSN. It listens for connections and 189 * processes any received requests. Loops until killed, for example by 190 * SIGTERM. 191 */ 192 public void run() 193 { 194 // Create socket for serving pages to clients 195 try { 196 ss = new ServerSocket(); 197 SocketAddress addr = new InetSocketAddress(PSN_PORT); 198 ss.setReuseAddress(true); 199 ss.bind(addr, DEFAULT_BACKLOG); 200 } catch (IOException ex) { 201 logger.error("Unable to bind to PSN socket.", ex); 202 System.exit(2); 203 } 204 205 // listen for incoming connections 206 Socket s = null; 207 while (!shutdown) { 208 try { 209 // blocks until a request is received 210 s = ss.accept(); 211 if ( s == null ) 212 continue; 213 214 DataOutputStream out = new DataOutputStream(s.getOutputStream() ); 215 DataInputStream in = new DataInputStream(s.getInputStream() ); 216 217 // readInt reads the int high byte first, as we require 218 int requestKey = in.readInt(); 219 logger.debug("Reponding to request with key " + requestKey); 220 221 byte[] page = pageDB.getPage(requestKey); 222 out.write(page); 223 224 s.close(); 225 } catch (IOException ex) { 226 logger.warn("Error reading page request; ignored.", ex); 227 } finally { 228 try { 229 if (s != null) 230 s.close(); 231 } catch (IOException e) { 232 // log and swallow the exception -- not much else can be done 233 logger.warn("Error closing socket; continuing anyway.", e); 234 } 235 } 236 } 237 } 238 239 /** 240 * This thread is run when the VM shuts down (via SIGTERM or otherwise). 241 * Writes the database to disk. 242 */ 243 private class PageServerNodeCleanupThread extends Thread 244 { 245 /** 246 * Just calls shutdownNow() on the enclosing PageServerNode 247 */ 248 public void run() 249 { 250 System.out.println("Shutting down"); 251 shutdownNow(); 252 } 253 } 254 } 255