1 /*
2  * Copyright 2013 The Netty Project
3  *
4  * The Netty Project licenses this file to you under the Apache License,
5  * version 2.0 (the "License"); you may not use this file except in compliance
6  * with the License. You may obtain a copy of the License at:
7  *
8  *   http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
12  * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13  * License for the specific language governing permissions and limitations
14  * under the License.
15  */

16
17 package io.netty.channel;
18
19 import io.netty.buffer.ByteBufUtil;
20 import io.netty.util.internal.EmptyArrays;
21 import io.netty.util.internal.MacAddressUtil;
22 import io.netty.util.internal.PlatformDependent;
23 import io.netty.util.internal.SystemPropertyUtil;
24 import io.netty.util.internal.logging.InternalLogger;
25 import io.netty.util.internal.logging.InternalLoggerFactory;
26
27 import java.lang.reflect.Method;
28 import java.util.Arrays;
29 import java.util.concurrent.atomic.AtomicInteger;
30
31 import static io.netty.util.internal.MacAddressUtil.defaultMachineId;
32 import static io.netty.util.internal.MacAddressUtil.parseMAC;
33
34 /**
35  * The default {@link ChannelId} implementation.
36  */

37 public final class DefaultChannelId implements ChannelId {
38
39     private static final long serialVersionUID = 3884076183504074063L;
40
41     private static final InternalLogger logger = InternalLoggerFactory.getInstance(DefaultChannelId.class);
42     private static final byte[] MACHINE_ID;
43     private static final int PROCESS_ID_LEN = 4;
44     private static final int PROCESS_ID;
45     private static final int SEQUENCE_LEN = 4;
46     private static final int TIMESTAMP_LEN = 8;
47     private static final int RANDOM_LEN = 4;
48
49     private static final AtomicInteger nextSequence = new AtomicInteger();
50
51     /**
52      * Returns a new {@link DefaultChannelId} instance.
53      */

54     public static DefaultChannelId newInstance() {
55         return new DefaultChannelId();
56     }
57
58     static {
59         int processId = -1;
60         String customProcessId = SystemPropertyUtil.get("io.netty.processId");
61         if (customProcessId != null) {
62             try {
63                 processId = Integer.parseInt(customProcessId);
64             } catch (NumberFormatException e) {
65                 // Malformed input.
66             }
67
68             if (processId < 0) {
69                 processId = -1;
70                 logger.warn("-Dio.netty.processId: {} (malformed)", customProcessId);
71             } else if (logger.isDebugEnabled()) {
72                 logger.debug("-Dio.netty.processId: {} (user-set)", processId);
73             }
74         }
75
76         if (processId < 0) {
77             processId = defaultProcessId();
78             if (logger.isDebugEnabled()) {
79                 logger.debug("-Dio.netty.processId: {} (auto-detected)", processId);
80             }
81         }
82
83         PROCESS_ID = processId;
84
85         byte[] machineId = null;
86         String customMachineId = SystemPropertyUtil.get("io.netty.machineId");
87         if (customMachineId != null) {
88             try {
89                 machineId = parseMAC(customMachineId);
90             } catch (Exception e) {
91                 logger.warn("-Dio.netty.machineId: {} (malformed)", customMachineId, e);
92             }
93             if (machineId != null) {
94                 logger.debug("-Dio.netty.machineId: {} (user-set)", customMachineId);
95             }
96         }
97
98         if (machineId == null) {
99             machineId = defaultMachineId();
100             if (logger.isDebugEnabled()) {
101                 logger.debug("-Dio.netty.machineId: {} (auto-detected)", MacAddressUtil.formatAddress(machineId));
102             }
103         }
104
105         MACHINE_ID = machineId;
106     }
107
108     private static int defaultProcessId() {
109         ClassLoader loader = null;
110         String value;
111         try {
112             loader = PlatformDependent.getClassLoader(DefaultChannelId.class);
113             // Invoke java.lang.management.ManagementFactory.getRuntimeMXBean().getName()
114             Class<?> mgmtFactoryType = Class.forName("java.lang.management.ManagementFactory"true, loader);
115             Class<?> runtimeMxBeanType = Class.forName("java.lang.management.RuntimeMXBean"true, loader);
116
117             Method getRuntimeMXBean = mgmtFactoryType.getMethod("getRuntimeMXBean", EmptyArrays.EMPTY_CLASSES);
118             Object bean = getRuntimeMXBean.invoke(null, EmptyArrays.EMPTY_OBJECTS);
119             Method getName = runtimeMxBeanType.getMethod("getName", EmptyArrays.EMPTY_CLASSES);
120             value = (String) getName.invoke(bean, EmptyArrays.EMPTY_OBJECTS);
121         } catch (Throwable t) {
122             logger.debug("Could not invoke ManagementFactory.getRuntimeMXBean().getName(); Android?", t);
123             try {
124                 // Invoke android.os.Process.myPid()
125                 Class<?> processType = Class.forName("android.os.Process"true, loader);
126                 Method myPid = processType.getMethod("myPid", EmptyArrays.EMPTY_CLASSES);
127                 value = myPid.invoke(null, EmptyArrays.EMPTY_OBJECTS).toString();
128             } catch (Throwable t2) {
129                 logger.debug("Could not invoke Process.myPid(); not Android?", t2);
130                 value = "";
131             }
132         }
133
134         int atIndex = value.indexOf('@');
135         if (atIndex >= 0) {
136             value = value.substring(0, atIndex);
137         }
138
139         int pid;
140         try {
141             pid = Integer.parseInt(value);
142         } catch (NumberFormatException e) {
143             // value did not contain an integer.
144             pid = -1;
145         }
146
147         if (pid < 0) {
148             pid = PlatformDependent.threadLocalRandom().nextInt();
149             logger.warn("Failed to find the current process ID from '{}'; using a random value: {}",  value, pid);
150         }
151
152         return pid;
153     }
154
155     private final byte[] data;
156     private final int hashCode;
157
158     private transient String shortValue;
159     private transient String longValue;
160
161     private DefaultChannelId() {
162         data = new byte[MACHINE_ID.length + PROCESS_ID_LEN + SEQUENCE_LEN + TIMESTAMP_LEN + RANDOM_LEN];
163         int i = 0;
164
165         // machineId
166         System.arraycopy(MACHINE_ID, 0, data, i, MACHINE_ID.length);
167         i += MACHINE_ID.length;
168
169         // processId
170         i = writeInt(i, PROCESS_ID);
171
172         // sequence
173         i = writeInt(i, nextSequence.getAndIncrement());
174
175         // timestamp (kind of)
176         i = writeLong(i, Long.reverse(System.nanoTime()) ^ System.currentTimeMillis());
177
178         // random
179         int random = PlatformDependent.threadLocalRandom().nextInt();
180         i = writeInt(i, random);
181         assert i == data.length;
182
183         hashCode = Arrays.hashCode(data);
184     }
185
186     private int writeInt(int i, int value) {
187         data[i ++] = (byte) (value >>> 24);
188         data[i ++] = (byte) (value >>> 16);
189         data[i ++] = (byte) (value >>> 8);
190         data[i ++] = (byte) value;
191         return i;
192     }
193
194     private int writeLong(int i, long value) {
195         data[i ++] = (byte) (value >>> 56);
196         data[i ++] = (byte) (value >>> 48);
197         data[i ++] = (byte) (value >>> 40);
198         data[i ++] = (byte) (value >>> 32);
199         data[i ++] = (byte) (value >>> 24);
200         data[i ++] = (byte) (value >>> 16);
201         data[i ++] = (byte) (value >>> 8);
202         data[i ++] = (byte) value;
203         return i;
204     }
205
206     @Override
207     public String asShortText() {
208         String shortValue = this.shortValue;
209         if (shortValue == null) {
210             this.shortValue = shortValue = ByteBufUtil.hexDump(data, data.length - RANDOM_LEN, RANDOM_LEN);
211         }
212         return shortValue;
213     }
214
215     @Override
216     public String asLongText() {
217         String longValue = this.longValue;
218         if (longValue == null) {
219             this.longValue = longValue = newLongValue();
220         }
221         return longValue;
222     }
223
224     private String newLongValue() {
225         StringBuilder buf = new StringBuilder(2 * data.length + 5);
226         int i = 0;
227         i = appendHexDumpField(buf, i, MACHINE_ID.length);
228         i = appendHexDumpField(buf, i, PROCESS_ID_LEN);
229         i = appendHexDumpField(buf, i, SEQUENCE_LEN);
230         i = appendHexDumpField(buf, i, TIMESTAMP_LEN);
231         i = appendHexDumpField(buf, i, RANDOM_LEN);
232         assert i == data.length;
233         return buf.substring(0, buf.length() - 1);
234     }
235
236     private int appendHexDumpField(StringBuilder buf, int i, int length) {
237         buf.append(ByteBufUtil.hexDump(data, i, length));
238         buf.append('-');
239         i += length;
240         return i;
241     }
242
243     @Override
244     public int hashCode() {
245         return hashCode;
246     }
247
248     @Override
249     public int compareTo(final ChannelId o) {
250         if (this == o) {
251             // short circuit
252             return 0;
253         }
254         if (o instanceof DefaultChannelId) {
255             // lexicographic comparison
256             final byte[] otherData = ((DefaultChannelId) o).data;
257             int len1 = data.length;
258             int len2 = otherData.length;
259             int len = Math.min(len1, len2);
260
261             for (int k = 0; k < len; k++) {
262                 byte x = data[k];
263                 byte y = otherData[k];
264                 if (x != y) {
265                     // treat these as unsigned bytes for comparison
266                     return (x & 0xff) - (y & 0xff);
267                 }
268             }
269             return len1 - len2;
270         }
271
272         return asLongText().compareTo(o.asLongText());
273     }
274
275     @Override
276     public boolean equals(Object obj) {
277         if (this == obj) {
278             return true;
279         }
280         if (!(obj instanceof DefaultChannelId)) {
281             return false;
282         }
283         DefaultChannelId other = (DefaultChannelId) obj;
284         return hashCode == other.hashCode && Arrays.equals(data, other.data);
285     }
286
287     @Override
288     public String toString() {
289         return asShortText();
290     }
291 }
292