001    package edu.harvard.deas.hyperenc;
002    
003    import java.io.DataInputStream;
004    import java.io.DataOutputStream;
005    import java.io.IOException;
006    import java.net.InetAddress;
007    import java.net.Socket;
008    import java.net.SocketTimeoutException;
009    import java.net.UnknownHostException;
010    
011    import org.apache.log4j.Logger;
012    
013    import edu.harvard.deas.hyperenc.util.NNLookup;
014    
015    /**
016     * The VSatRandomSource, given two 32-bit integers, the <i>PSN selection key</i>
017     * and the <i>page request key</i>, obtains a page of random data from a PSN. It
018     * selects the PSN from a PSN list specified at construction, and then sends the
019     * page request key to the PSN, which replies by sending a page.
020     */
021    public class VSatRandomSource implements RandomSource 
022    {
023      private static final long serialVersionUID = 1L;
024      
025      private static final Logger logger = Logger.getLogger(VSatRandomSource.class);
026    
027      /**
028       * The port on which PSNs listen by default; connections to PSNs will attempt
029       * to connect to this port.
030       */ 
031      public static final int DEFAULT_PSN_PORT = 48369;
032              
033      private Socket socket = null;
034      private static final int SOCKET_TIMEOUT = 2000;  // 2 seconds
035      
036      private NNLookup<String> psnList;
037    
038      /**
039       * Constructs a new VSatRandomSource.
040       * 
041       * @param psnList
042       *        a list of PSN hostnames/IPs with associated integer keys. This list
043       *        will be used to choose PSNs; the PSN with the numerically closest
044       *        key, per the {@link NNLookup#get} method, is used.
045       */
046      public VSatRandomSource(NNLookup<String> psnList) {
047        this.psnList = psnList;
048      }
049      
050      /**
051       * Chooses a PSN using the given key.
052       * @param psnID PSN selection key
053       * @return the hostname (or IP address, as a String) of a PSN
054       */
055      private String selectPSN(int psnID) {
056        return psnList.get(psnID);
057      }
058    
059      /**
060       * Gets a page of random bits.
061       * 
062       * @param block
063       *        A buffer of random bytes used used to get the PSN info/id from the
064       *        central server (must be exactly 8 bytes long)
065       * @param page
066       *        An empty array to hold the returned random data
067       */
068      @Override
069      public void getPage(byte [] block, byte [] page) throws PageCreationException
070      {
071        // Use first 4 bytes of block to determine PSN ID
072        byte[] firstHalf = new byte[4];
073        for (int i = 0; i < 4; i++)
074          firstHalf[i] = block[i];
075        int psnKey = bytesToInt(firstHalf);
076        
077        String psnHostname = selectPSN(psnKey);
078        assert(psnHostname != null);
079        
080        InetAddress psnIP;
081        try {
082          psnIP = InetAddress.getByName(psnHostname);
083        } catch (UnknownHostException e) {
084          logger.warn("Could not resolve PSN hostname " + psnHostname);
085          throw new PageCreationException("Could not resolve PSN hostname "
086              + psnHostname);
087        }
088        
089        // Use last 4 bytes of block to determine request key to send to PSN
090        byte[] lastHalf = new byte[4];
091        for (int i = 0; i < 4; i++)
092          lastHalf[i] = block[i + 4];
093        int requestKey = bytesToInt(lastHalf);
094        
095        try {
096          logger.debug("Requesting page from PSN located at " + psnIP);
097          socket = new Socket(psnIP, DEFAULT_PSN_PORT);
098          
099          socket.setSoTimeout(SOCKET_TIMEOUT);
100            
101          DataInputStream in = new DataInputStream(socket.getInputStream());
102          DataOutputStream out = new DataOutputStream(socket.getOutputStream());
103          
104          out.writeInt(requestKey);
105          out.flush();
106          in.read(page);
107        } catch (SocketTimeoutException ex) {
108          logger.warn("Socket timed out; aborting PSN request");
109          throw new PageCreationException("Socket timeout", ex);
110        } catch (IOException ex) {
111          throw new PageCreationException("I/O error contacting PSN", ex);
112        } finally {
113          try {
114            socket.close();
115          } catch (IOException e) {
116            // ignore -- can't really do anything about it
117            logger.warn("Error closing socket; ignoring and continuing", e);
118          }
119        }
120      }
121    
122      /**
123       * Concatenate the 4 bytes of <code>b</code> into an int. This function
124       * computes the 32-bit integer equal to the sum, for <i>k</i> from 0 to 3, of
125       * 2<sup>3-k</sup>*<code>b[k]</code>.
126       * <p>
127       * All bytes are treated as unsigned; the result of concatenating the bytes is
128       * treated as a two's complement integer and returned.
129       * 
130       * @param b
131       *        A byte array.
132       * @return The integer as described above.
133       * @throws IllegalArgumentException
134       *         if <code>b</code> is not exactly 4 bytes long
135       */
136      private int bytesToInt(byte[] b) {
137        if (b.length != 4)
138          throw new IllegalArgumentException(
139              "byte array b must contain exactly 4 bytes");
140        
141        // ints are 32 bits, bytes are 8.
142        int result = 0;
143        for (int i = 0; i < 4; i++) {
144          result <<= 8;
145          int unsignedbyte = b[i];
146          if (unsignedbyte < 0)
147            unsignedbyte += 256;
148          result += unsignedbyte;
149        }
150        return result;
151      }
152    }