View Javadoc
1   /*
2    * This file is part of Waarp Project (named also Waarp or GG).
3    *
4    *  Copyright (c) 2019, Waarp SAS, and individual contributors by the @author
5    *  tags. See the COPYRIGHT.txt in the distribution for a full listing of
6    * individual contributors.
7    *
8    *  All Waarp Project is free software: you can redistribute it and/or
9    * modify it under the terms of the GNU General Public License as published by
10   * the Free Software Foundation, either version 3 of the License, or (at your
11   * option) any later version.
12   *
13   * Waarp is distributed in the hope that it will be useful, but WITHOUT ANY
14   * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
15   * A PARTICULAR PURPOSE. See the GNU General Public License for more details.
16   *
17   *  You should have received a copy of the GNU General Public License along with
18   * Waarp . If not, see <http://www.gnu.org/licenses/>.
19   */
20  
21  package org.waarp.common.guid;
22  
23  import com.fasterxml.jackson.annotation.JsonGetter;
24  import com.fasterxml.jackson.annotation.JsonIgnore;
25  import com.fasterxml.jackson.annotation.JsonSetter;
26  import com.fasterxml.jackson.annotation.JsonTypeInfo;
27  import org.waarp.common.exception.InvalidArgumentException;
28  import org.waarp.common.utility.BaseXx;
29  import org.waarp.common.utility.SingletonUtils;
30  
31  import java.util.Arrays;
32  import java.util.concurrent.atomic.AtomicInteger;
33  
34  @JsonTypeInfo(use = JsonTypeInfo.Id.CLASS, include = JsonTypeInfo.As.PROPERTY, property = "@class")
35  public class GUID implements Comparable<GUID> {
36    /**
37     * ARK header
38     */
39    public static final String ARK = "ark:/";
40  
41    private static final String ATTEMPTED_TO_PARSE_MALFORMED_ARK_GUID =
42        "Attempted to parse malformed ARK GUID: ";
43    /**
44     * Native size of the GUID
45     */
46    static final int KEYSIZE = 21;
47    static final int KEYB64SIZE = 28;
48    static final int KEYB32SIZE = 34;
49    static final int KEYB16SIZE = KEYSIZE * 2;
50    static final int HEADER_POS = 0;
51    static final int HEADER_SIZE = 1;
52    static final int TENANT_POS = 1;
53    static final int TENANT_SIZE = 4;
54    static final int PLATFORM_POS = 5;
55    static final int PLATFORM_SIZE = 4;
56    static final int PID_POS = 9;
57    static final int PID_SIZE = 3;
58    static final int TIME_POS = 12;
59    static final int TIME_SIZE = 6;
60    static final int COUNTER_POS = 18;
61    static final int COUNTER_SIZE = 3;
62    /**
63     * Bits size of Counter
64     */
65    private static final int SIZE_COUNTER = COUNTER_SIZE * 8;
66    /**
67     * Min Counter value
68     */
69    private static final int MIN_COUNTER = 0;
70    /**
71     * Max Counter value
72     */
73    private static final int MAX_COUNTER = (1 << SIZE_COUNTER) - 1;
74    /**
75     * Version to store (to check correctness if future algorithm) between 0 and
76     * 255
77     */
78    static final int VERSION = 1 & 0xFF;
79  
80    static final int BYTE_SIZE = 8;
81    /**
82     * Counter part
83     */
84    private static final AtomicInteger COUNTER = new AtomicInteger(MIN_COUNTER);
85    /**
86     * real GUID
87     */
88    @JsonIgnore
89    private final byte[] bguid;
90  
91    static final synchronized int getNewCounter() {
92      if (COUNTER.compareAndSet(MAX_COUNTER, MIN_COUNTER)) {
93        return MAX_COUNTER;
94      } else {
95        return COUNTER.getAndIncrement();
96      }
97    }
98  
99    /**
100    * @return the KeySize
101    */
102   public static int getKeySize() {
103     return KEYSIZE;
104   }
105 
106 
107   /**
108    * Internal constructor
109    *
110    * @param size size of the byte representation
111    */
112   GUID(final int size) {
113     bguid = new byte[size];
114   }
115 
116   /**
117    * Constructor that takes a byte array as this GUID's content
118    *
119    * @param bytes GUID content
120    *
121    * @throws InvalidArgumentException
122    */
123   public GUID(final byte[] bytes) throws InvalidArgumentException {
124     this(KEYSIZE);
125     setBytes(bytes, KEYSIZE);
126     if (getVersion() != VERSION) {
127       throw new InvalidArgumentException(
128           "Version is incorrect: " + getVersion());
129     }
130   }
131 
132   /**
133    * Build from String key
134    *
135    * @param idsource
136    *
137    * @throws InvalidArgumentException
138    */
139   public GUID(final String idsource) throws InvalidArgumentException {
140     this(KEYSIZE);
141     setString(idsource);
142     if (getVersion() != VERSION) {
143       throw new InvalidArgumentException(
144           "Version is incorrect: " + getVersion());
145     }
146   }
147 
148   /**
149    * Internal function
150    *
151    * @param bytes
152    * @param size size of the byte representation
153    *
154    * @return this
155    *
156    * @throws InvalidArgumentException
157    */
158   @JsonIgnore
159   GUID setBytes(final byte[] bytes, final int size)
160       throws InvalidArgumentException {
161     if (bytes == null) {
162       throw new InvalidArgumentException("Empty argument");
163     }
164     if (bytes.length != size) {
165       throw new InvalidArgumentException(
166           "Attempted to parse malformed GUID: (" + bytes.length + ") " +
167           Arrays.toString(bytes));
168     }
169     System.arraycopy(bytes, 0, bguid, 0, size);
170     return this;
171   }
172 
173   /**
174    * Constructor that generates a new GUID using the current process id,
175    * Platform Id and timestamp with no tenant
176    */
177   public GUID() {
178     this(0, JvmProcessId.macAddressAsInt() & 0x7FFFFFFF);
179   }
180 
181   /**
182    * Constructor that generates a new GUID using the current process id and
183    * timestamp
184    *
185    * @param tenantId tenant id between 0 and 2^30-1
186    * @param platformId platform Id between 0 and 2^31-1
187    *
188    * @throws IllegalArgumentException if any of the argument are out
189    *     of range
190    */
191   public GUID(final int tenantId, final int platformId) {
192     this(KEYSIZE);
193     if (tenantId < 0 || tenantId > 0x3FFFFFFF) {
194       throw new IllegalArgumentException(
195           "DomainId must be between 0 and 2^30-1: " + tenantId);
196     }
197     if (platformId < 0 || platformId > 0x7FFFFFFF) {
198       throw new IllegalArgumentException(
199           "PlatformId must be between 0 and 2^31-1: " + platformId);
200     }
201 
202     // atomically
203     final long time = System.currentTimeMillis();
204     final int count = getNewCounter();
205     // 1 bytes = Version (8)
206     bguid[HEADER_POS] = (byte) VERSION;
207 
208     // 4 bytes - 2 bits = Domain (30)
209     int value = tenantId;
210     bguid[TENANT_POS + 3] = (byte) (value & 0xFF);
211     value >>>= BYTE_SIZE;
212     bguid[TENANT_POS + 2] = (byte) (value & 0xFF);
213     value >>>= BYTE_SIZE;
214     bguid[TENANT_POS + 1] = (byte) (value & 0xFF);
215     value >>>= BYTE_SIZE;
216     bguid[TENANT_POS] = (byte) (value & 0x3F);
217 
218     // 4 bytes = -1 bit Platform (31)
219     value = platformId;
220     bguid[PLATFORM_POS + 3] = (byte) (value & 0xFF);
221     value >>>= BYTE_SIZE;
222     bguid[PLATFORM_POS + 2] = (byte) (value & 0xFF);
223     value >>>= BYTE_SIZE;
224     bguid[PLATFORM_POS + 1] = (byte) (value & 0xFF);
225     value >>>= BYTE_SIZE;
226     bguid[PLATFORM_POS] = (byte) (value & 0x7F);
227 
228     // 3 bytes = -2 bits JVMPID (22)
229     value = JvmProcessId.JVMPID;
230     bguid[PID_POS + 2] = (byte) (value & 0xFF);
231     value >>>= BYTE_SIZE;
232     bguid[PID_POS + 1] = (byte) (value & 0xFF);
233     value >>>= BYTE_SIZE;
234     bguid[PID_POS] = (byte) (value & 0xFF);
235 
236     // 6 bytes = timestamp (so up to 8 925 years after Time 0 so year 10
237     // 895)
238     long lvalue = time;
239     bguid[TIME_POS + 5] = (byte) (lvalue & 0xFF);
240     lvalue >>>= BYTE_SIZE;
241     bguid[TIME_POS + 4] = (byte) (lvalue & 0xFF);
242     lvalue >>>= BYTE_SIZE;
243     bguid[TIME_POS + 3] = (byte) (lvalue & 0xFF);
244     lvalue >>>= BYTE_SIZE;
245     bguid[TIME_POS + 2] = (byte) (lvalue & 0xFF);
246     lvalue >>>= BYTE_SIZE;
247     bguid[TIME_POS + 1] = (byte) (lvalue & 0xFF);
248     lvalue >>>= BYTE_SIZE;
249     bguid[TIME_POS] = (byte) (lvalue & 0xFF);
250 
251     // 3 bytes = counter against collision
252     value = count;
253     bguid[COUNTER_POS + 2] = (byte) (value & 0xFF);
254     value >>>= BYTE_SIZE;
255     bguid[COUNTER_POS + 1] = (byte) (value & 0xFF);
256     value >>>= BYTE_SIZE;
257     bguid[COUNTER_POS] = (byte) (value & 0xFF);
258 
259   }
260 
261   /**
262    * @return the Base32 representation (default of toString)
263    */
264   @JsonIgnore
265   public final String toBase32() {
266     return BaseXx.getBase32(bguid);
267   }
268 
269   /**
270    * @return the Base64 representation (default of toString)
271    */
272   @JsonIgnore
273   public final String toBase64() {
274     return BaseXx.getBase64UrlWithoutPadding(bguid);
275   }
276 
277   /**
278    * @return the Hexadecimal representation
279    */
280   @JsonIgnore
281   public final String toHex() {
282     return BaseXx.getBase16(bguid);
283   }
284 
285   /**
286    * @return the Ark representation of this GUID
287    */
288   @JsonIgnore
289   public final String toArk() {
290     return new StringBuilder(ARK).append(getTenantId()).append('/')
291                                  .append(toArkName()).toString();
292   }
293 
294   /**
295    * @return the String representation of this GUID
296    */
297   @JsonGetter("id")
298   public final String getId() {
299     return toString();
300   }
301 
302   /**
303    * Internal function
304    *
305    * @param idsource
306    *
307    * @return this
308    *
309    * @throws InvalidArgumentException
310    */
311   @JsonSetter("id")
312   GUID setString(final String idsource) throws InvalidArgumentException {
313     if (idsource == null) {
314       throw new InvalidArgumentException("Empty argument");
315     }
316     final String id = idsource.trim();
317     if (idsource.startsWith(ARK)) {
318       String ids = idsource;
319       ids = ids.substring(ARK.length());
320       final int separator = ids.indexOf('/');
321       if (separator <= 0) {
322         throw new InvalidArgumentException(
323             ATTEMPTED_TO_PARSE_MALFORMED_ARK_GUID + id);
324       }
325       int tenantId;
326       try {
327         tenantId = Integer.parseInt(ids.substring(0, separator));
328       } catch (final NumberFormatException e) {
329         throw new InvalidArgumentException(
330             ATTEMPTED_TO_PARSE_MALFORMED_ARK_GUID + id);
331       }
332       // BASE32
333       ids = ids.substring(separator + 1);
334       final byte[] base32 = BaseXx.getFromBase32(ids);
335       if (base32.length != KEYSIZE - TENANT_SIZE) {
336         throw new InvalidArgumentException(
337             ATTEMPTED_TO_PARSE_MALFORMED_ARK_GUID + id);
338       }
339       System.arraycopy(base32, 0, bguid, HEADER_POS, HEADER_SIZE);
340       // GUID Domain default to 0 (from 0 to 2^30-1)
341       bguid[TENANT_POS + 3] = (byte) (tenantId & 0xFF);
342       tenantId >>>= BYTE_SIZE;
343       bguid[TENANT_POS + 2] = (byte) (tenantId & 0xFF);
344       tenantId >>>= BYTE_SIZE;
345       bguid[TENANT_POS + 1] = (byte) (tenantId & 0xFF);
346       tenantId >>>= BYTE_SIZE;
347       bguid[TENANT_POS] = (byte) (tenantId & 0x3F);
348       // BASE32
349       System.arraycopy(base32, HEADER_SIZE, bguid, PLATFORM_POS,
350                        PLATFORM_SIZE + PID_SIZE + TIME_SIZE + COUNTER_SIZE);
351       return this;
352     }
353     final int len = id.length();
354     try {
355       if (len == KEYB16SIZE) {
356         // HEXA BASE16
357         System.arraycopy(BaseXx.getFromBase16(idsource), 0, bguid, 0, KEYSIZE);
358       } else if (len == KEYB32SIZE) {
359         // BASE32
360         System.arraycopy(BaseXx.getFromBase32(idsource), 0, bguid, 0, KEYSIZE);
361       } else if (len == KEYB64SIZE) {
362         // BASE64
363         System.arraycopy(BaseXx.getFromBase64UrlWithoutPadding(idsource), 0,
364                          bguid, 0, KEYSIZE);
365       } else {
366         throw new InvalidArgumentException(
367             "Attempted to parse malformed GUID: (" + len + ") " + id);
368       }
369     } catch (final IllegalArgumentException e) {
370       throw new InvalidArgumentException(
371           "Attempted to parse malformed GUID: " + id, e);
372     }
373     return this;
374   }
375 
376   /**
377    * extract version field as a hex char from raw GUID bytes
378    *
379    * @return version char
380    */
381   @JsonIgnore
382   public final int getVersion() {
383     return bguid[HEADER_POS] & 0xFF;
384   }
385 
386   /**
387    * @return the Tenant Id of GUID from which it belongs to (default being 0)
388    */
389   @JsonIgnore
390   public final int getTenantId() {
391     return (bguid[TENANT_POS] & 0x3F) << BYTE_SIZE * 3 |
392            (bguid[TENANT_POS + 1] & 0xFF) << BYTE_SIZE * 2 |
393            (bguid[TENANT_POS + 2] & 0xFF) << BYTE_SIZE |
394            bguid[TENANT_POS + 3] & 0xFF;
395   }
396 
397   /**
398    * Extract Platform id as int. Could be using partial MAC address.
399    *
400    * @return the Platform id as int, or -1 for unrecognized format
401    */
402   @JsonIgnore
403   public final int getPlatformId() {
404     return (bguid[PLATFORM_POS] & 0x7F) << BYTE_SIZE * 3 |
405            (bguid[PLATFORM_POS + 1] & 0xFF) << BYTE_SIZE * 2 |
406            (bguid[PLATFORM_POS + 2] & 0xFF) << BYTE_SIZE |
407            bguid[PLATFORM_POS + 3] & 0xFF;
408   }
409 
410   /**
411    * Extract Platform id as bytes. Could be using partial MAC address.
412    *
413    * @return byte array of GUID fragment, or null for unrecognized format
414    */
415   @JsonIgnore
416   public final byte[] getMacFragment() {
417     if (getVersion() != VERSION) {
418       return SingletonUtils.getSingletonByteArray();
419     }
420     final byte[] x = new byte[6];
421     x[0] = 0;
422     x[1] = 0;
423     x[2] = (byte) (bguid[PLATFORM_POS] & 0x7F);
424     x[3] = bguid[PLATFORM_POS + 1];
425     x[4] = bguid[PLATFORM_POS + 2];
426     x[5] = bguid[PLATFORM_POS + 3];
427     return x;
428   }
429 
430   /**
431    * Extract process id and return as int
432    *
433    * @return id of process that generated the GUID, or -1 for unrecognized
434    *     format
435    */
436   @JsonIgnore
437   public final int getProcessId() {
438     if (getVersion() != VERSION) {
439       return -1;
440     }
441     return (bguid[PID_POS] & 0xFF) << BYTE_SIZE * 2 |
442            (bguid[PID_POS + 1] & 0xFF) << BYTE_SIZE | bguid[PID_POS + 2] & 0xFF;
443   }
444 
445   /**
446    * Extract timestamp and return as long
447    *
448    * @return millisecond UTC timestamp from generation of the GUID, or -1 for
449    *     unrecognized format
450    */
451   @JsonIgnore
452   public final long getTimestamp() {
453     if (getVersion() != VERSION) {
454       return -1;
455     }
456     long time = 0;
457     for (int i = 0; i < TIME_SIZE; i++) {
458       time <<= BYTE_SIZE;
459       time |= bguid[TIME_POS + i] & 0xFF;
460     }
461     return time;
462   }
463 
464   /**
465    * @return the associated counter against collision value
466    */
467   @JsonIgnore
468   public final int getCounter() {
469     return (bguid[COUNTER_POS] & 0xFF) << BYTE_SIZE * 2 |
470            (bguid[COUNTER_POS + 1] & 0xFF) << BYTE_SIZE |
471            bguid[COUNTER_POS + 2] & 0xFF;
472   }
473 
474   /**
475    * @return the Ark Name part of Ark representation
476    */
477   public final String toArkName() {
478     final byte[] temp = new byte[KEYSIZE - TENANT_SIZE];
479     System.arraycopy(bguid, HEADER_POS, temp, 0, HEADER_SIZE);
480     System.arraycopy(bguid, PLATFORM_POS, temp, HEADER_SIZE,
481                      PLATFORM_SIZE + PID_SIZE + TIME_SIZE + COUNTER_SIZE);
482     return BaseXx.getBase32(temp);
483   }
484 
485 
486   @Override
487   public String toString() {
488     return toBase32();
489   }
490 
491   /**
492    * copy the uuid of this GUID, so that it can't be changed, and return it
493    *
494    * @return raw byte array of GUID
495    */
496   @JsonIgnore
497   public final byte[] getBytes() {
498     return Arrays.copyOf(bguid, bguid.length);
499   }
500 
501   @Override
502   @JsonIgnore
503   public final int hashCode() {
504     return Arrays.hashCode(bguid);
505   }
506 
507   @Override
508   public final boolean equals(final Object o) {
509     if (!(o instanceof GUID)) {
510       return false;
511     }
512     return this == o || Arrays.equals(bguid, ((GUID) o).bguid);
513   }
514 
515   @Override
516   public final int compareTo(final GUID guid) {
517     final int id = getTenantId();
518     final int id2 = guid.getTenantId();
519     if (id != id2) {
520       return id < id2? -1 : 1;
521     }
522     final long ts = getTimestamp();
523     final long ts2 = guid.getTimestamp();
524     if (ts == ts2) {
525       final int ct = getCounter();
526       final int ct2 = guid.getCounter();
527       if (ct == ct2) {
528         // then all must be equals, else whatever
529         return Arrays.equals(this.bguid, guid.getBytes())? 0 : -1;
530       }
531       // Cannot be equal
532       return ct < ct2? -1 : 1;
533     }
534     // others as ProcessId or Platform are unimportant in comparison
535     return ts < ts2? -1 : 1;
536   }
537 
538 }