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