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 }