View Javadoc
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    *   https://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.ThreadLocalRandom;
30  import java.util.concurrent.atomic.AtomicInteger;
31  
32  import static io.netty.util.internal.MacAddressUtil.defaultMachineId;
33  import static io.netty.util.internal.MacAddressUtil.parseMAC;
34  import static io.netty.util.internal.PlatformDependent.BIG_ENDIAN_NATIVE_ORDER;
35  
36  /**
37   * The default {@link ChannelId} implementation.
38   */
39  public final class DefaultChannelId implements ChannelId {
40  
41      private static final long serialVersionUID = 3884076183504074063L;
42  
43      private static final InternalLogger logger = InternalLoggerFactory.getInstance(DefaultChannelId.class);
44      private static final byte[] MACHINE_ID;
45      private static final int PROCESS_ID_LEN = 4;
46      private static final int PROCESS_ID;
47      private static final int SEQUENCE_LEN = 4;
48      private static final int TIMESTAMP_LEN = 8;
49      private static final int RANDOM_LEN = 4;
50  
51      private static final AtomicInteger nextSequence = new AtomicInteger();
52  
53      /**
54       * Returns a new {@link DefaultChannelId} instance.
55       */
56      public static DefaultChannelId newInstance() {
57          return new DefaultChannelId(MACHINE_ID,
58                                      PROCESS_ID,
59                                      nextSequence.getAndIncrement(),
60                                      Long.reverse(System.nanoTime()) ^ System.currentTimeMillis(),
61                                      ThreadLocalRandom.current().nextInt());
62      }
63  
64      static {
65          int processId = -1;
66          String customProcessId = SystemPropertyUtil.get("io.netty.processId");
67          if (customProcessId != null) {
68              try {
69                  processId = Integer.parseInt(customProcessId);
70              } catch (NumberFormatException e) {
71                  // Malformed input.
72              }
73  
74              if (processId < 0) {
75                  processId = -1;
76                  logger.warn("-Dio.netty.processId: {} (malformed)", customProcessId);
77              } else if (logger.isDebugEnabled()) {
78                  logger.debug("-Dio.netty.processId: {} (user-set)", processId);
79              }
80          }
81  
82          if (processId < 0) {
83              processId = defaultProcessId();
84              if (logger.isDebugEnabled()) {
85                  logger.debug("-Dio.netty.processId: {} (auto-detected)", processId);
86              }
87          }
88  
89          PROCESS_ID = processId;
90  
91          byte[] machineId = null;
92          String customMachineId = SystemPropertyUtil.get("io.netty.machineId");
93          if (customMachineId != null) {
94              try {
95                  machineId = parseMAC(customMachineId);
96              } catch (Exception e) {
97                  logger.warn("-Dio.netty.machineId: {} (malformed)", customMachineId, e);
98              }
99              if (machineId != null) {
100                 logger.debug("-Dio.netty.machineId: {} (user-set)", customMachineId);
101             }
102         }
103 
104         if (machineId == null) {
105             machineId = defaultMachineId();
106             if (logger.isDebugEnabled()) {
107                 logger.debug("-Dio.netty.machineId: {} (auto-detected)", MacAddressUtil.formatAddress(machineId));
108             }
109         }
110 
111         MACHINE_ID = machineId;
112     }
113 
114     static int processHandlePid(ClassLoader loader) {
115         // pid is positive on unix, non{-1,0} on windows
116         int nilValue = -1;
117         if (PlatformDependent.javaVersion() >= 9) {
118             Long pid;
119             try {
120                 Class<?> processHandleImplType = Class.forName("java.lang.ProcessHandle", true, loader);
121                 Method processHandleCurrent = processHandleImplType.getMethod("current");
122                 Object processHandleInstance = processHandleCurrent.invoke(null);
123                 Method processHandlePid = processHandleImplType.getMethod("pid");
124                 pid = (Long) processHandlePid.invoke(processHandleInstance);
125             } catch (Exception e) {
126                 logger.debug("Could not invoke ProcessHandle.current().pid();", e);
127                 return nilValue;
128             }
129             if (pid > Integer.MAX_VALUE || pid < Integer.MIN_VALUE) {
130                 throw new IllegalStateException("Current process ID exceeds int range: " + pid);
131             }
132             return pid.intValue();
133         }
134         return nilValue;
135     }
136 
137     static int jmxPid(ClassLoader loader) {
138         String value;
139         try {
140             // Invoke java.lang.management.ManagementFactory.getRuntimeMXBean().getName()
141             Class<?> mgmtFactoryType = Class.forName("java.lang.management.ManagementFactory", true, loader);
142             Class<?> runtimeMxBeanType = Class.forName("java.lang.management.RuntimeMXBean", true, loader);
143 
144             Method getRuntimeMXBean = mgmtFactoryType.getMethod("getRuntimeMXBean", EmptyArrays.EMPTY_CLASSES);
145             Object bean = getRuntimeMXBean.invoke(null, EmptyArrays.EMPTY_OBJECTS);
146             Method getName = runtimeMxBeanType.getMethod("getName", EmptyArrays.EMPTY_CLASSES);
147             value = (String) getName.invoke(bean, EmptyArrays.EMPTY_OBJECTS);
148         } catch (Throwable t) {
149             logger.debug("Could not invoke ManagementFactory.getRuntimeMXBean().getName(); Android?", t);
150             try {
151                 // Invoke android.os.Process.myPid()
152                 Class<?> processType = Class.forName("android.os.Process", true, loader);
153                 Method myPid = processType.getMethod("myPid", EmptyArrays.EMPTY_CLASSES);
154                 value = myPid.invoke(null, EmptyArrays.EMPTY_OBJECTS).toString();
155             } catch (Throwable t2) {
156                 logger.debug("Could not invoke Process.myPid(); not Android?", t2);
157                 value = "";
158             }
159         }
160 
161         int atIndex = value.indexOf('@');
162         if (atIndex >= 0) {
163             value = value.substring(0, atIndex);
164         }
165 
166         int pid;
167         try {
168             pid = Integer.parseInt(value);
169         } catch (NumberFormatException e) {
170             // value did not contain an integer.
171             pid = -1;
172         }
173 
174         if (pid < 0) {
175             pid = ThreadLocalRandom.current().nextInt();
176             logger.warn("Failed to find the current process ID from '{}'; using a random value: {}",  value, pid);
177         }
178 
179         return pid;
180     }
181 
182     static int defaultProcessId() {
183         ClassLoader loader = PlatformDependent.getClassLoader(DefaultChannelId.class);
184         int processId = processHandlePid(loader);
185         if (processId != -1) {
186             return processId;
187         }
188         return jmxPid(loader);
189     }
190 
191     private final byte[] data;
192     private final int hashCode;
193 
194     private transient String shortValue;
195     private transient String longValue;
196 
197     /**
198      * Visible for testing
199      */
200     DefaultChannelId(final byte[] machineId, final int processId, final int sequence,
201                              final long timestamp, final int random) {
202         final byte[] data = new byte[machineId.length + PROCESS_ID_LEN + SEQUENCE_LEN + TIMESTAMP_LEN + RANDOM_LEN];
203         int i = 0;
204 
205         // machineId
206         System.arraycopy(machineId, 0, data, i, machineId.length);
207         i += machineId.length;
208 
209         // processId
210         writeInt(data, i, processId);
211         i += Integer.BYTES;
212 
213         // sequence
214         writeInt(data, i, sequence);
215         i += Integer.BYTES;
216 
217         // timestamp (kind of)
218         writeLong(data, i, timestamp);
219         i += Long.BYTES;
220 
221         // random
222         writeInt(data, i, random);
223         i += Integer.BYTES;
224         assert i == data.length;
225 
226         this.data = data;
227         hashCode = Arrays.hashCode(data);
228     }
229 
230     private static void writeInt(byte[] data, int i, int value) {
231         if (PlatformDependent.isUnaligned()) {
232             PlatformDependent.putInt(data, i, BIG_ENDIAN_NATIVE_ORDER ? value : Integer.reverseBytes(value));
233             return;
234         }
235         data[i] = (byte) (value >>> 24);
236         data[i + 1] = (byte) (value >>> 16);
237         data[i + 2] = (byte) (value >>> 8);
238         data[i + 3] = (byte) value;
239     }
240 
241     private static void writeLong(byte[] data, int i, long value) {
242         if (PlatformDependent.isUnaligned()) {
243             PlatformDependent.putLong(data, i, BIG_ENDIAN_NATIVE_ORDER ? value : Long.reverseBytes(value));
244             return;
245         }
246         data[i] = (byte) (value >>> 56);
247         data[i + 1] = (byte) (value >>> 48);
248         data[i + 2] = (byte) (value >>> 40);
249         data[i + 3] = (byte) (value >>> 32);
250         data[i + 4] = (byte) (value >>> 24);
251         data[i + 5] = (byte) (value >>> 16);
252         data[i + 6] = (byte) (value >>> 8);
253         data[i + 7] = (byte) value;
254     }
255 
256     @Override
257     public String asShortText() {
258         String shortValue = this.shortValue;
259         if (shortValue == null) {
260             this.shortValue = shortValue = ByteBufUtil.hexDump(data, data.length - RANDOM_LEN, RANDOM_LEN);
261         }
262         return shortValue;
263     }
264 
265     @Override
266     public String asLongText() {
267         String longValue = this.longValue;
268         if (longValue == null) {
269             this.longValue = longValue = newLongValue();
270         }
271         return longValue;
272     }
273 
274     private String newLongValue() {
275         final StringBuilder buf = new StringBuilder(2 * data.length + 5);
276         final int machineIdLen = data.length - PROCESS_ID_LEN - SEQUENCE_LEN - TIMESTAMP_LEN - RANDOM_LEN;
277         int i = 0;
278         i = appendHexDumpField(buf, i, machineIdLen);
279         i = appendHexDumpField(buf, i, PROCESS_ID_LEN);
280         i = appendHexDumpField(buf, i, SEQUENCE_LEN);
281         i = appendHexDumpField(buf, i, TIMESTAMP_LEN);
282         i = appendHexDumpField(buf, i, RANDOM_LEN);
283         assert i == data.length;
284         return buf.substring(0, buf.length() - 1);
285     }
286 
287     private int appendHexDumpField(StringBuilder buf, int i, int length) {
288         buf.append(ByteBufUtil.hexDump(data, i, length));
289         buf.append('-');
290         i += length;
291         return i;
292     }
293 
294     @Override
295     public int hashCode() {
296         return hashCode;
297     }
298 
299     @Override
300     public int compareTo(final ChannelId o) {
301         if (this == o) {
302             // short circuit
303             return 0;
304         }
305         if (o instanceof DefaultChannelId) {
306             // lexicographic comparison
307             final byte[] otherData = ((DefaultChannelId) o).data;
308             int len1 = data.length;
309             int len2 = otherData.length;
310             int len = Math.min(len1, len2);
311 
312             for (int k = 0; k < len; k++) {
313                 byte x = data[k];
314                 byte y = otherData[k];
315                 if (x != y) {
316                     // treat these as unsigned bytes for comparison
317                     return (x & 0xff) - (y & 0xff);
318                 }
319             }
320             return len1 - len2;
321         }
322 
323         return asLongText().compareTo(o.asLongText());
324     }
325 
326     @Override
327     public boolean equals(Object obj) {
328         if (this == obj) {
329             return true;
330         }
331         if (!(obj instanceof DefaultChannelId)) {
332             return false;
333         }
334         DefaultChannelId other = (DefaultChannelId) obj;
335         return hashCode == other.hashCode && Arrays.equals(data, other.data);
336     }
337 
338     @Override
339     public String toString() {
340         return asShortText();
341     }
342 }