1 /*
2 * Licensed to the Apache Software Foundation (ASF) under one or more
3 * contributor license agreements. See the NOTICE file distributed with
4 * this work for additional information regarding copyright ownership.
5 * The ASF licenses this file to You under the Apache License, Version 2.0
6 * (the "License"); you may not use this file except in compliance with
7 * the License. You may obtain a copy of the License at
8 *
9 * http://www.apache.org/licenses/LICENSE-2.0
10 *
11 * Unless required by applicable law or agreed to in writing, software
12 * distributed under the License is distributed on an "AS IS" BASIS,
13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 * See the License for the specific language governing permissions and
15 * limitations under the License.
16 */
17
18 package org.apache.commons.id.uuid.state;
19
20 import java.io.IOException;
21 import java.net.InetAddress;
22 import java.net.UnknownHostException;
23 import java.util.Collection;
24 import java.util.Iterator;
25 import java.util.Random;
26 import java.util.StringTokenizer;
27
28 import org.apache.commons.id.DecoderException;
29 import org.apache.commons.discovery.tools.DiscoverClass;
30 import org.apache.commons.id.uuid.Bytes;
31 import org.apache.commons.id.uuid.Constants;
32 import org.apache.commons.id.uuid.clock.Clock;
33 import org.apache.commons.id.uuid.clock.OverClockedException;
34 import org.apache.commons.id.DigestUtils;
35 import org.apache.commons.id.Hex;
36
37 /**
38 * <p><code>StateHelper</code> provides helper methods for the uuid state
39 * implementations.</p>
40 *
41 * @author Commons-Id team
42 * @version $Id: StateHelper.java 480488 2006-11-29 08:57:26Z bayard $
43 */
44 public final class StateHelper implements Constants {
45 /** The key for the System.property containing the ClockImpl String. */
46 public static final String UUID_CLOCK_IMPL_PROPERTY_KEY = Clock.class.getName();
47
48 /** The key for the System.property containing the StateImpl String. */
49 public static final String UUID_STATE_IMPL_PROPERTY_KEY = State.class.getName();
50
51 /** Array length of node bytes. */
52 public static final int NODE_ID_BYTE_LENGTH = 6;
53
54 /** Number of bytes in a short. */
55 public static final short BYTES_IN_SHORT = 2;
56
57 /** Number of postitions to shift when shifting by one byte. */
58 public static final short SHIFT_BY_BYTE = 8;
59
60 /** Number of postitions to shift when shifting by one byte. */
61 public static final short HOSTNAME_MAX_CHAR_LEN = 255;
62
63 /** OR-Mask to set the node's multicast bit true. */
64 private static final int MULTICAST_BIT_SET = 0x80;
65
66 /** The maximum character length of a long */
67 private static final short LONG_CHAR_LEN = 19;
68
69 /** Standard one page buffer size */
70 private static final int BUF_PAGE_SZ = 1024;
71
72 /** Start of the XML document used to store state persistence in XML */
73 protected static final String XML_DOC_START = "<?xml version=\"1.0\""
74 + " encoding=\"UTF-8\" ?>"
75 + "\n<!DOCTYPE uuidstate [\n"
76 + " <!ELEMENT uuidstate (node*)>\n"
77 + " <!ELEMENT node EMPTY>\n"
78 + " <!ATTLIST node id ID #REQUIRED>\n"
79 + " <!ATTLIST node clocksequence CDATA #IMPLIED>\n"
80 + " <!ATTLIST node lasttimestamp CDATA #IMPLIED>\n]>"
81 + "\n<uuidstate synchInterval=\"";
82
83 /** End of document start */
84 protected static final String XML_DOC_START_END = "\">";
85 /** Start of XML node tag */
86 protected static final String XML_NODE_TAG_START = "\n\t<node id=\"";
87 /** After id of XML node tag */
88 protected static final String XML_NODE_TAG_AFTER_ID = "\" clocksequence=\"";
89 /** After clock sequence of XML node tag */
90 protected static final String XML_NODE_TAG_AFTER_CSEQ = "\" timestamp=\"";
91 /** End of XML node tag */
92 protected static final String XML_NODE_TAG_END = "\" />";
93 /** End of the XML document used to store state persistence in XML */
94 protected static final String XML_DOC_END = "\n</uuidstate>";
95
96 /** Number of tokens in a MAC address with "-" as separator */
97 private static final short MAC_ADDRESS_TOKEN_COUNT = 6;
98
99 /** String length of a MAC address with "-" as separator */
100 private static final short MAC_ADDRESS_CHAR_LENGTH = 17;
101
102 /** Default constructor */
103 private StateHelper() {
104 super();
105 }
106
107 /**
108 * <p>Creates a Random node identifier as described in IEFT UUID URN
109 * specification.</p>
110 *
111 * @return a random node idenfifier based on MD5 of system information.
112 */
113 public static byte[] randomNodeIdentifier() {
114 //Holds the 16 byte MD5 value
115 byte[] seed = new byte[UUID_BYTE_LENGTH];
116 //Set the initial string buffer capacity
117 //Time + Object.hashCode + HostName + Guess of all system properties
118 int bufSize = (LONG_CHAR_LEN * 2) + HOSTNAME_MAX_CHAR_LEN + (2 * BUF_PAGE_SZ);
119 StringBuffer randInfo = new StringBuffer(bufSize);
120 //Add current time
121 long time = 0;
122 try {
123 time = getClockImpl().getUUIDTime();
124 } catch (OverClockedException oce) {
125 time = System.currentTimeMillis();
126 }
127 randInfo.append(time);
128
129 //Add hostname
130 try {
131 InetAddress address = InetAddress.getLocalHost();
132 randInfo.append(address.getHostName());
133 } catch (UnknownHostException ukhe) {
134 randInfo.append("Host Unknown");
135 }
136 //Add something else "random"
137 randInfo.append(new Object().hashCode());
138
139 //Add system properties
140 Collection info = System.getProperties().values();
141 Iterator it = info.iterator();
142 while (it.hasNext()) {
143 randInfo.append(it.next());
144 }
145 //MD5 Hash code the system information to get a node id.
146 seed = DigestUtils.md5(randInfo.toString());
147
148 //Return upper 6 bytes of hash
149 byte[] raw = new byte[NODE_ID_BYTE_LENGTH];
150 System.arraycopy(seed, 0, raw, 0, NODE_ID_BYTE_LENGTH);
151
152 //Per draft set multi-cast bit true
153 raw[0] |= MULTICAST_BIT_SET;
154
155 return raw;
156 }
157
158 /**
159 * <p>Generates a new security quality random clock sequence.</p>
160 *
161 * @return a new security quality random clock sequence.
162 */
163 public static short newClockSequence() {
164 Random random = new Random();
165 byte[] bytes = new byte[BYTES_IN_SHORT];
166 random.nextBytes(bytes);
167 return (short) (Bytes.toShort(bytes) & 0x3FFF);
168 }
169
170 /**
171 * <p>Returns the Clock implementation using commons discovery.</p>
172 *
173 * @return the Clock implementation using commons discovery.
174 */
175 public static Clock getClockImpl() {
176 Clock c = null;
177 try {
178 DiscoverClass dc = new DiscoverClass();
179 c = (Clock) dc.newInstance(
180 Clock.class,
181 Clock.DEFAULT_CLOCK_IMPL);
182 } catch (Exception ex) {
183 // ignore as default implementation will be used.
184 }
185 return c;
186 }
187
188 /**
189 * <p>Returns the <code>State</code> implementation in use.</p>
190 *
191 * @return the <code>State</code> implementation in use.
192 */
193 public static State getStateImpl() {
194 State s = null;
195 try {
196 DiscoverClass dc = new DiscoverClass();
197 s = (State) dc.newInstance(
198 State.class,
199 State.DEFAULT_STATE_IMPL);
200 } catch (Exception ex) {
201 // ignore as default implementation will be used.
202 }
203 return s;
204 }
205
206 /**
207 * <p>Utility method decodes a valid MAC address in the form of
208 * XX-XX-XX-XX-XX-XX where each XX represents a hexidecimal value.</p>
209 *
210 * <p> Returns null if the address can not be decoded. </p>
211 *
212 * @param address the String hexidecimal dash separated MAC address.
213 * @return a byte array representing the the address. Null if not a valid address.
214 */
215 public static byte[] decodeMACAddress(String address) {
216 StringBuffer buf = new StringBuffer(MAC_ADDRESS_TOKEN_COUNT * 2);
217 StringTokenizer tokens = new StringTokenizer(address, "-");
218 if (tokens.countTokens() != MAC_ADDRESS_TOKEN_COUNT) {
219 return null;
220 } else {
221 for (int i = 0; i < MAC_ADDRESS_TOKEN_COUNT; i++) {
222 buf.append(tokens.nextToken());
223 }
224 }
225 try {
226 char[] c = buf.toString().toCharArray();
227 return Hex.decodeHex(c);
228 } catch (DecoderException de) {
229 de.printStackTrace();
230 return null;
231 }
232 }
233
234 /**
235 * <p>Returns the node id / address byte array in it's hexidecimal string
236 * representation in the following format: XX-XX-XX-XX-XX-XX where each XX
237 * represents a hexidecimal value.</p>
238 *
239 * @param address the 6 byte node id / address.
240 * @return the node id /address byte array in as hexidecimal with dash
241 * separating each octet.
242 * @throws IOException an Input Output Exception.
243 */
244 public static String encodeMACAddress(byte[] address) throws IOException {
245 char[] chars = Hex.encodeHex(address);
246 StringBuffer buf = new StringBuffer(MAC_ADDRESS_CHAR_LENGTH);
247 for (int i = 0; i < chars.length; i++) {
248 buf.append(chars[i]);
249 if (i != chars.length - 1 && i % 2 != 0) {
250 buf.append("-");
251 }
252 }
253 return buf.toString().toUpperCase();
254 }
255 }