View Javadoc

1   /*
2    * Copyright (c) 2004 UNINETT FAS
3    *
4    * This program is free software; you can redistribute it and/or modify it
5    * under the terms of the GNU General Public License as published by the Free
6    * Software Foundation; either version 2 of the License, or (at your option)
7    * any later version.
8    *
9    * This program is distributed in the hope that it will be useful, but WITHOUT
10   * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
11   * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
12   * more details.
13   *
14   * You should have received a copy of the GNU General Public License along with
15   * this program; if not, write to the Free Software Foundation, Inc., 59 Temple
16   * Place - Suite 330, Boston, MA 02111-1307, USA.
17   *
18   * $Id: RandomId.java,v 1.11 2005/03/29 09:26:24 cato Exp $
19   */
20  
21  package no.feide.moria.store;
22  
23  import java.security.NoSuchAlgorithmException;
24  import java.security.SecureRandom;
25  import java.util.Date;
26  
27  /***
28   * Returns an id that is random and unique across a cluster of JVMs. Each JVM
29   * needs to be configured with a unique node id, identifying each node. This is
30   * done by setting the system property <code>no.feide.moria.store.nodeid</code>.
31   * The value must be a ascii string of 3 character length. The returned id is an
32   * encoded String (pseudo Base64, see method documentation for details)
33   * constructed from the node id, the current time and a random string. This
34   * should guarantee unique ids across the cluster and node restarts.
35   * @author Bj&oslash;rn Ola Smievoll &lt;b.o@smievoll.no&gt;
36   * @version $Revision: 1.11 $
37   */
38  // TODO: Update the description. No longer correct - doesn't need to set a node ID.
39  public final class RandomId {
40  
41      /*** The random generator. */
42      private static SecureRandom random;
43  
44      static {
45          /* Initiate the random generator */
46          try {
47              random = SecureRandom.getInstance("SHA1PRNG");
48          } catch (NoSuchAlgorithmException e) {
49              throw new RuntimeException("Unable to get instance of SecureRandom", e);
50          }
51      }
52  
53      /*** The characters used in our version of base64. */
54      private static final byte[] CHAR_64 = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789*-".getBytes();
55  
56      /*** The number of random bits to get from the PRNG. */
57      private static final int NO_OF_RANDOM_BITS = 384;
58  
59  
60      /***
61       * Default private constructor.
62       */
63      private RandomId() {
64  
65      }
66  
67  
68      /***
69       * Generate a new random id.
70       *
71       * @return an encoded random string
72       */
73      public static String newId(final String nodeId) {
74  
75          final byte[] nodeIdBytes = nodeIdToByteArray(nodeId);
76  
77          /* Get timestamp */
78          byte[] now = longToByteArray(new Date().getTime());
79  
80          /* Round up number of bytes if the bits don't divide by eight */
81          int noOfRandomBytes = NO_OF_RANDOM_BITS / 8;
82  
83          if ((NO_OF_RANDOM_BITS % 8) != 0)
84              noOfRandomBytes++;
85  
86          /* Get the randomness */
87          byte[] randomBytes = new byte[noOfRandomBytes];
88          random.nextBytes(randomBytes);
89  
90          /* Build the complete id */
91          byte[] id = new byte[nodeIdBytes.length + now.length + randomBytes.length];
92          System.arraycopy(nodeIdBytes, 0, id, 0, nodeIdBytes.length);
93          System.arraycopy(now, 0, id, nodeIdBytes.length, now.length);
94          System.arraycopy(randomBytes, 0, id, nodeIdBytes.length + now.length, randomBytes.length);
95  
96          /* Encode and return the id */
97          return pseudoBase64Encode(id);
98      }
99  
100 
101     /***
102      * Takes a byte array and returns a string encoded with a slightly modified
103      * version of Base64. The difference to standard Base64 is that the
104      * extra two chars, "+" and "/" have been exchanged for the more
105      * url-friendly "-" and "*", and the resulting string is not padded with =
106      * as required by the spec (rfc 2045). Parts of code Copyright (c) 2003,
107      * Sverre H. Huseby &lt;shh@thathost.com&gt;
108      * @param bytes
109      *            The data to convert.
110      * @return The encoded version of the input.
111      */
112     static String pseudoBase64Encode(final byte[] bytes) {
113 
114         /* The final id string, initial size is 4/3 larger than input */
115         StringBuffer finalId = new StringBuffer((bytes.length * 4) / 3);
116 
117         /* Holds the current element offset of the input byte array */
118         int byteOffset;
119         /* Holds the value of the current element of the input byte array */
120         int currentByte;
121         /* Used to extract values from the character array */
122         int charOffset;
123 
124         /*
125          * Three input bytes will give four output characters by grouping six
126          * and six bits
127          */
128         for (byteOffset = 0;;) {
129 
130             /* Six first bits of the first of three bytes */
131             if (byteOffset >= bytes.length)
132                 break;
133             currentByte = ((int) bytes[byteOffset++]) & 255;
134             charOffset = currentByte >> 2;
135             finalId.append((char) CHAR_64[charOffset]);
136 
137             /* Two last bits of the first byte and four bits of the second byte */
138             charOffset = (currentByte & 3) << 4;
139             if (byteOffset < bytes.length) {
140                 currentByte = ((int) bytes[byteOffset++]) & 255;
141                 charOffset |= currentByte >> 4;
142                 finalId.append((char) CHAR_64[charOffset]);
143             } else {
144                 finalId.append((char) CHAR_64[charOffset]);
145                 break;
146             }
147 
148             /* Four last bits of the second byte and two of the third */
149             charOffset = (currentByte & 15) << 2;
150             if (byteOffset < bytes.length) {
151                 currentByte = ((int) bytes[byteOffset++]) & 255;
152                 charOffset |= currentByte >> 6;
153                 finalId.append((char) CHAR_64[charOffset]);
154             } else {
155                 finalId.append((char) CHAR_64[charOffset]);
156                 break;
157             }
158 
159             /* Last six bits of the third and final byte */
160             charOffset = currentByte & 63;
161             finalId.append((char) CHAR_64[charOffset]);
162         }
163 
164         return finalId.toString();
165     }
166 
167 
168     /***
169      * Takes a long value (64 bit) and returns it as an eight element byte
170      * array.
171      * @param in
172      *            The long value to be converted.
173      * @return A byte array representation of the long value given as input.
174      */
175     static byte[] longToByteArray(final long in) {
176 
177         /* Make a copy that we can harass. */
178         long local = in;
179 
180         /* Java long is 64 bits, 8 byte */
181         final int longSize = 8;
182         byte[] out = new byte[longSize];
183 
184         /*
185          * "And" the long value with 255 to effectively reduce it to a byte,
186          * then cast. As we operate on the least significant bits we populate
187          * the array backwards. Right shift eight bits so they may be
188          * "extracted" next iteration.
189          */
190         for (int i = longSize - 1; i > -1; i--) {
191             out[i] = (byte) (local & 0xffL);
192             local >>= 8;
193         }
194 
195         return out;
196     }
197 
198     /***
199      * Takes an unsigned short value (16 bit, as an 32 bit int value) and returns it as
200      * an two element byte array.
201      *
202      * @param in
203      *          the unsigned short value to be converted. 
204      * @return a byte array representation of the short value given as input
205      */
206     static byte[] unsignedShortToByteArray(final int in) {
207 
208         if (in < 0 || in > 65535) {
209             throw new IllegalArgumentException("in value must be between 0 and 2^16.");
210         }
211 
212         /* Make a copy that we can harass. */
213         int local = in;
214 
215         /* short is 16 bits, 2 byte */
216         final int shortSize = 2;
217         byte[] out = new byte[shortSize];
218 
219         /* Add first 16 bits to byte array. Ignore remaing as we 
220          * start with the least signifcant bits. */
221         for (int i = shortSize - 1; i > -1; i--) {
222             out[i] = (byte) (local & 0xffL);
223             local >>= 8;
224         }
225 
226         return out;
227     }
228 
229     static byte[] nodeIdToByteArray(final String nodeId) {
230 
231         final String errorMsg = "nodeId has illegal form.  Must be <ip-addr>:<port>.";
232 
233         /* Split address from port. */
234         String[] nodeElements = nodeId.split(":");
235 
236         if (nodeElements.length != 2) {
237             throw new IllegalArgumentException(errorMsg);
238         }
239 
240         /* Parse address part. */
241         String[] addressBytes = nodeElements[0].split("//.");
242 
243         if (addressBytes.length != 4) {
244             throw new IllegalArgumentException(errorMsg);
245         }
246 
247         byte[] buffer = new byte[6];
248 
249         for (int i = 0; i < 4; i++) {
250             /* Subtract 128 to get the value into the signed range. */
251             buffer[i] = Byte.parseByte(Integer.toString(Integer.parseInt(addressBytes[i]) - 128));
252         }
253 
254         /* Parse port part. */
255         byte[] portBytes = unsignedShortToByteArray(Integer.parseInt(nodeElements[1]));
256 
257         if (portBytes.length != 2) {
258             throw new IllegalArgumentException(errorMsg);
259         }
260 
261         for (int i = 0; i < 2; i++) {
262             buffer[i + 4] = portBytes[i];
263         }
264 
265         return buffer;
266     }
267 }