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