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.util.internal.EmptyArrays;
20  import io.netty.util.internal.MacAddressUtil;
21  import io.netty.util.internal.PlatformDependent;
22  import io.netty.util.internal.SystemPropertyUtil;
23  import io.netty.util.internal.logging.InternalLogger;
24  import io.netty.util.internal.logging.InternalLoggerFactory;
25  
26  import java.lang.reflect.Method;
27  import java.util.Arrays;
28  import java.util.concurrent.ThreadLocalRandom;
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 = 809640043754842613L;
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      private static final char[] HEX_CHARS = "0123456789abcdef".toCharArray();
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[] machineId;
192     private final int processId;
193     private final int sequence;
194     private final long timestamp;
195     private final int random;
196     private final int hashCode;
197 
198     private transient String shortValue;
199     private transient String longValue;
200 
201     /**
202      * Visible for testing
203      */
204     DefaultChannelId(final byte[] machineId, final int processId, final int sequence,
205                      final long timestamp, final int random) {
206         this.machineId = machineId;
207         this.processId = processId;
208         this.sequence = sequence;
209         this.timestamp = timestamp;
210         this.random = random;
211         hashCode = computeHashCode();
212     }
213 
214     private int computeHashCode() {
215         int h = Arrays.hashCode(machineId);
216         h = 31 * h + processId;
217         h = 31 * h + sequence;
218         h = 31 * h + Long.hashCode(timestamp);
219         h = 31 * h + random;
220         return h;
221     }
222 
223     @Override
224     public String asShortText() {
225         String shortValue = this.shortValue;
226         if (shortValue == null) {
227             final StringBuilder buf = new StringBuilder(RANDOM_LEN * 2);
228             appendHexInt(buf, random);
229             this.shortValue = shortValue = buf.toString();
230         }
231         return shortValue;
232     }
233 
234     @Override
235     public String asLongText() {
236         String longValue = this.longValue;
237         if (longValue == null) {
238             this.longValue = longValue = newLongValue();
239         }
240         return longValue;
241     }
242 
243     private String newLongValue() {
244         final int machineIdLen = machineId.length;
245         final StringBuilder buf = new StringBuilder(
246                 2 * (machineIdLen + PROCESS_ID_LEN + SEQUENCE_LEN + TIMESTAMP_LEN + RANDOM_LEN) + 4);
247         appendHexBytes(buf, machineId);
248         buf.append('-');
249         appendHexInt(buf, processId);
250         buf.append('-');
251         appendHexInt(buf, sequence);
252         buf.append('-');
253         appendHexLong(buf, timestamp);
254         buf.append('-');
255         appendHexInt(buf, random);
256         return buf.toString();
257     }
258 
259     private static void appendHexBytes(StringBuilder buf, byte[] bytes) {
260         for (byte b : bytes) {
261             buf.append(HEX_CHARS[(b & 0xFF) >>> 4]);
262             buf.append(HEX_CHARS[b & 0xF]);
263         }
264     }
265 
266     private static void appendHexInt(StringBuilder buf, int value) {
267         for (int i = 28; i >= 0; i -= 4) {
268             buf.append(HEX_CHARS[(value >>> i) & 0xF]);
269         }
270     }
271 
272     private static void appendHexLong(StringBuilder buf, long value) {
273         for (int i = 60; i >= 0; i -= 4) {
274             buf.append(HEX_CHARS[(int) ((value >>> i) & 0xF)]);
275         }
276     }
277 
278     @Override
279     public int hashCode() {
280         return hashCode;
281     }
282 
283     @Override
284     public int compareTo(final ChannelId o) {
285         if (this == o) {
286             // short circuit
287             return 0;
288         }
289         if (o instanceof DefaultChannelId) {
290             final DefaultChannelId other = (DefaultChannelId) o;
291             int cmp = compareBytes(machineId, other.machineId);
292             if (cmp != 0) {
293                 return cmp;
294             }
295             cmp = Integer.compareUnsigned(processId, other.processId);
296             if (cmp != 0) {
297                 return cmp;
298             }
299             cmp = Integer.compareUnsigned(sequence, other.sequence);
300             if (cmp != 0) {
301                 return cmp;
302             }
303             cmp = Long.compareUnsigned(timestamp, other.timestamp);
304             if (cmp != 0) {
305                 return cmp;
306             }
307             return Integer.compareUnsigned(random, other.random);
308         }
309 
310         return asLongText().compareTo(o.asLongText());
311     }
312 
313     private static int compareBytes(byte[] a, byte[] b) {
314         int len1 = a.length;
315         int len2 = b.length;
316         int len = Math.min(len1, len2);
317         for (int k = 0; k < len; k++) {
318             int cmp = (a[k] & 0xFF) - (b[k] & 0xFF);
319             if (cmp != 0) {
320                 return cmp;
321             }
322         }
323         return len1 - len2;
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
336                 && random == other.random
337                 && processId == other.processId
338                 && sequence == other.sequence
339                 && timestamp == other.timestamp
340                 && Arrays.equals(machineId, other.machineId);
341     }
342 
343     @Override
344     public String toString() {
345         return asShortText();
346     }
347 }