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.netty5.channel;
18  
19  import io.netty5.buffer.BufferUtil;
20  import io.netty5.util.internal.MacAddressUtil;
21  import io.netty5.util.internal.SystemPropertyUtil;
22  import io.netty5.util.internal.logging.InternalLogger;
23  import io.netty5.util.internal.logging.InternalLoggerFactory;
24  
25  import java.util.Arrays;
26  import java.util.concurrent.ThreadLocalRandom;
27  import java.util.concurrent.atomic.AtomicInteger;
28  
29  import static io.netty5.util.internal.MacAddressUtil.defaultMachineId;
30  import static io.netty5.util.internal.MacAddressUtil.parseMAC;
31  
32  /**
33   * The default {@link ChannelId} implementation.
34   */
35  public final class DefaultChannelId implements ChannelId {
36  
37      private static final long serialVersionUID = 3884076183504074063L;
38  
39      private static final InternalLogger logger = InternalLoggerFactory.getInstance(DefaultChannelId.class);
40      private static final byte[] MACHINE_ID;
41      private static final int PROCESS_ID_LEN = 4;
42      private static final int PROCESS_ID;
43      private static final int SEQUENCE_LEN = 4;
44      private static final int TIMESTAMP_LEN = 8;
45      private static final int RANDOM_LEN = 4;
46  
47      private static final AtomicInteger nextSequence = new AtomicInteger();
48  
49      /**
50       * Returns a new {@link DefaultChannelId} instance.
51       */
52      public static DefaultChannelId newInstance() {
53          return new DefaultChannelId();
54      }
55  
56      static {
57          int processId = -1;
58          String customProcessId = SystemPropertyUtil.get("io.netty5.processId");
59          if (customProcessId != null) {
60              try {
61                  processId = Integer.parseInt(customProcessId);
62              } catch (NumberFormatException e) {
63                  // Malformed input.
64              }
65  
66              if (processId < 0) {
67                  processId = -1;
68                  logger.warn("-Dio.netty5.processId: {} (malformed)", customProcessId);
69              } else if (logger.isDebugEnabled()) {
70                  logger.debug("-Dio.netty5.processId: {} (user-set)", processId);
71              }
72          }
73  
74          if (processId < 0) {
75              processId = defaultProcessId();
76              if (logger.isDebugEnabled()) {
77                  logger.debug("-Dio.netty5.processId: {} (auto-detected)", processId);
78              }
79          }
80  
81          PROCESS_ID = processId;
82  
83          byte[] machineId = null;
84          String customMachineId = SystemPropertyUtil.get("io.netty5.machineId");
85          if (customMachineId != null) {
86              try {
87                  machineId = parseMAC(customMachineId);
88              } catch (Exception e) {
89                  logger.warn("-Dio.netty5.machineId: {} (malformed)", customMachineId, e);
90              }
91              if (machineId != null) {
92                  logger.debug("-Dio.netty5.machineId: {} (user-set)", customMachineId);
93              }
94          }
95  
96          if (machineId == null) {
97              machineId = defaultMachineId();
98              if (logger.isDebugEnabled()) {
99                  logger.debug("-Dio.netty5.machineId: {} (auto-detected)", MacAddressUtil.formatAddress(machineId));
100             }
101         }
102 
103         MACHINE_ID = machineId;
104     }
105 
106     private static int defaultProcessId() {
107         long pid = ProcessHandle.current().pid();
108         if (pid > Integer.MAX_VALUE || pid < Integer.MIN_VALUE) {
109             logger.warn("Current process ID exceeds int range: " + pid);
110             pid = ThreadLocalRandom.current().nextInt();
111         }
112         return (int) pid;
113     }
114 
115     private final byte[] data;
116     private final int hashCode;
117 
118     private transient String shortValue;
119     private transient String longValue;
120 
121     private DefaultChannelId() {
122         data = new byte[MACHINE_ID.length + PROCESS_ID_LEN + SEQUENCE_LEN + TIMESTAMP_LEN + RANDOM_LEN];
123         int i = 0;
124 
125         // machineId
126         System.arraycopy(MACHINE_ID, 0, data, i, MACHINE_ID.length);
127         i += MACHINE_ID.length;
128 
129         // processId
130         i = writeInt(i, PROCESS_ID);
131 
132         // sequence
133         i = writeInt(i, nextSequence.getAndIncrement());
134 
135         // timestamp (kind of)
136         i = writeLong(i, Long.reverse(System.nanoTime()) ^ System.currentTimeMillis());
137 
138         // random
139         int random = ThreadLocalRandom.current().nextInt();
140         i = writeInt(i, random);
141         assert i == data.length;
142 
143         hashCode = Arrays.hashCode(data);
144     }
145 
146     private int writeInt(int i, int value) {
147         data[i ++] = (byte) (value >>> 24);
148         data[i ++] = (byte) (value >>> 16);
149         data[i ++] = (byte) (value >>> 8);
150         data[i ++] = (byte) value;
151         return i;
152     }
153 
154     private int writeLong(int i, long value) {
155         data[i ++] = (byte) (value >>> 56);
156         data[i ++] = (byte) (value >>> 48);
157         data[i ++] = (byte) (value >>> 40);
158         data[i ++] = (byte) (value >>> 32);
159         data[i ++] = (byte) (value >>> 24);
160         data[i ++] = (byte) (value >>> 16);
161         data[i ++] = (byte) (value >>> 8);
162         data[i ++] = (byte) value;
163         return i;
164     }
165 
166     @Override
167     public String asShortText() {
168         String shortValue = this.shortValue;
169         if (shortValue == null) {
170             this.shortValue = shortValue = BufferUtil.hexDump(data, data.length - RANDOM_LEN, RANDOM_LEN);
171         }
172         return shortValue;
173     }
174 
175     @Override
176     public String asLongText() {
177         String longValue = this.longValue;
178         if (longValue == null) {
179             this.longValue = longValue = newLongValue();
180         }
181         return longValue;
182     }
183 
184     private String newLongValue() {
185         StringBuilder buf = new StringBuilder(2 * data.length + 5);
186         int i = 0;
187         i = appendHexDumpField(buf, i, MACHINE_ID.length);
188         i = appendHexDumpField(buf, i, PROCESS_ID_LEN);
189         i = appendHexDumpField(buf, i, SEQUENCE_LEN);
190         i = appendHexDumpField(buf, i, TIMESTAMP_LEN);
191         i = appendHexDumpField(buf, i, RANDOM_LEN);
192         assert i == data.length;
193         return buf.substring(0, buf.length() - 1);
194     }
195 
196     private int appendHexDumpField(StringBuilder buf, int i, int length) {
197         buf.append(BufferUtil.hexDump(data, i, length));
198         buf.append('-');
199         i += length;
200         return i;
201     }
202 
203     @Override
204     public int hashCode() {
205         return hashCode;
206     }
207 
208     @Override
209     public int compareTo(final ChannelId o) {
210         if (this == o) {
211             // short circuit
212             return 0;
213         }
214         if (o instanceof DefaultChannelId) {
215             // lexicographic comparison
216             final byte[] otherData = ((DefaultChannelId) o).data;
217             int len1 = data.length;
218             int len2 = otherData.length;
219             int len = Math.min(len1, len2);
220 
221             for (int k = 0; k < len; k++) {
222                 byte x = data[k];
223                 byte y = otherData[k];
224                 if (x != y) {
225                     // treat these as unsigned bytes for comparison
226                     return (x & 0xff) - (y & 0xff);
227                 }
228             }
229             return len1 - len2;
230         }
231 
232         return asLongText().compareTo(o.asLongText());
233     }
234 
235     @Override
236     public boolean equals(Object obj) {
237         if (this == obj) {
238             return true;
239         }
240         if (!(obj instanceof DefaultChannelId)) {
241             return false;
242         }
243         DefaultChannelId other = (DefaultChannelId) obj;
244         return hashCode == other.hashCode && Arrays.equals(data, other.data);
245     }
246 
247     @Override
248     public String toString() {
249         return asShortText();
250     }
251 }