/*******************************************************************************
* Copyright French Prime minister Office/SGMAP/DINSIC/Vitam Program (2015-2019)
*
* contact.vitam@culture.gouv.fr
*
* This software is a computer program whose purpose is to implement a digital archiving back-office system managing
* high volumetry securely and efficiently.
*
* This software is governed by the CeCILL 2.1 license under French law and abiding by the rules of distribution of free
* software. You can use, modify and/ or redistribute the software under the terms of the CeCILL 2.1 license as
* circulated by CEA, CNRS and INRIA at the following URL "http://www.cecill.info".
*
* As a counterpart to the access to the source code and rights to copy, modify and redistribute granted by the license,
* users are provided only with a limited warranty and the software's author, the holder of the economic rights, and the
* successive licensors have only limited liability.
*
* In this respect, the user's attention is drawn to the risks associated with loading, using, modifying and/or
* developing or reproducing the software by the user in light of its specific status of free software, that may mean
* that it is complicated to manipulate, and that also therefore means that it is reserved for developers and
* experienced professionals having in-depth computer knowledge. Users are therefore encouraged to load and test the
* software's suitability as regards their requirements in conditions enabling the security of their systems and/or data
* to be ensured and, more generally, to use and operate it in the same conditions as regards security.
*
* The fact that you are presently reading this means that you have had knowledge of the CeCILL 2.1 license and that you
* accept its terms.
*******************************************************************************/
package fr.gouv.vitam.common.guid;
import java.util.Arrays;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonSetter;
import fr.gouv.vitam.common.BaseXx;
import fr.gouv.vitam.common.SingletonUtils;
import fr.gouv.vitam.common.exception.InvalidGuidOperationException;
/**
* GUID Reader (Global Unique Identifier Reader) <br>
*
*/
class GUIDImpl extends GUIDAbstract {
private static final String ATTEMPTED_TO_PARSE_MALFORMED_ARK_GUID = "Attempted to parse malformed ARK GUID: ";
/**
* Native size of the GUID
*/
static final int KEYSIZE = 22;
static final int KEYB64SIZE = 30;
static final int KEYB32SIZE = 36;
static final int KEYB16SIZE = KEYSIZE * 2;
static final int HEADER_POS = 0;
static final int HEADER_SIZE = 2;
static final int TENANT_POS = 2;
static final int TENANT_SIZE = 4;
static final int PLATFORM_POS = 6;
static final int PLATFORM_SIZE = 4;
static final int PID_POS = 10;
static final int PID_SIZE = 3;
static final int TIME_POS = 13;
static final int TIME_SIZE = 6;
static final int COUNTER_POS = 19;
static final int COUNTER_SIZE = 3;
/**
* Version to store (to check correctness if future algorithm) between 0 and 255
*/
static final int VERSION = 1 & 0xFF;
static final int BYTE_SIZE = 8;
/**
* Empty constructor for Json support
*/
GUIDImpl() {
super(KEYSIZE);
}
/**
* Constructor that takes a byte array as this GUID's content
*
* @param bytes GUID content
* @throws InvalidGuidOperationException
*/
GUIDImpl(final byte[] bytes) throws InvalidGuidOperationException {
super(KEYSIZE);
setBytes(bytes, KEYSIZE);
if (getVersion() != VERSION) {
throw new InvalidGuidOperationException("Version is incorrect: " + getVersion());
}
}
/**
* Build from String key
*
* @param idsource
* @throws InvalidGuidOperationException
*/
GUIDImpl(final String idsource) throws InvalidGuidOperationException {
super(KEYSIZE);
setString(idsource);
if (getVersion() != VERSION) {
throw new InvalidGuidOperationException("Version is incorrect: " + getVersion());
}
}
/**
* @return the KeySize
*/
public static int getKeySize() {
return KEYSIZE;
}
/**
* Internal function
*
* @param idsource
* @return this
* @throws InvalidGuidOperationException
*/
@JsonSetter("id")
GUIDImpl setString(final String idsource)
throws InvalidGuidOperationException {
if (idsource == null) {
throw new InvalidGuidOperationException("Empty argument");
}
final String id = idsource.trim();
if (idsource.startsWith(ARK)) {
String ids = idsource;
ids = ids.substring(ARK.length());
final int separator = ids.indexOf('/');
if (separator <= 0) {
throw new InvalidGuidOperationException(
ATTEMPTED_TO_PARSE_MALFORMED_ARK_GUID + id);
}
int tenantId;
try {
tenantId = Integer.parseInt(ids.substring(0, separator));
} catch (final NumberFormatException e) {
throw new InvalidGuidOperationException(
ATTEMPTED_TO_PARSE_MALFORMED_ARK_GUID + id);
}
// BASE32
ids = ids.substring(separator + 1);
final byte[] base32 = BaseXx.getFromBase32(ids);
if (base32.length != KEYSIZE - TENANT_SIZE) {
throw new InvalidGuidOperationException(
ATTEMPTED_TO_PARSE_MALFORMED_ARK_GUID + id);
}
System.arraycopy(base32, 0, guid, HEADER_POS, HEADER_SIZE);
// GUID Domain default to 0 (from 0 to 2^30-1)
guid[TENANT_POS + 3] = (byte) (tenantId & 0xFF);
tenantId >>>= BYTE_SIZE;
guid[TENANT_POS + 2] = (byte) (tenantId & 0xFF);
tenantId >>>= BYTE_SIZE;
guid[TENANT_POS + 1] = (byte) (tenantId & 0xFF);
tenantId >>>= BYTE_SIZE;
guid[TENANT_POS] = (byte) (tenantId & 0x3F);
// BASE32
System.arraycopy(base32, HEADER_SIZE, guid, PLATFORM_POS,
PLATFORM_SIZE + PID_SIZE + TIME_SIZE + COUNTER_SIZE);
return this;
}
final int len = id.length();
try {
if (len == KEYB16SIZE) {
// HEXA BASE16
System.arraycopy(BaseXx.getFromBase16(idsource), 0, guid, 0, KEYSIZE);
} else if (len == KEYB32SIZE) {
// BASE32
System.arraycopy(BaseXx.getFromBase32(idsource), 0, guid, 0, KEYSIZE);
} else if (len == KEYB64SIZE) {
// BASE64
System.arraycopy(BaseXx.getFromBase64UrlWithoutPadding(idsource), 0, guid, 0, KEYSIZE);
} else {
throw new InvalidGuidOperationException(
"Attempted to parse malformed GUID: (" + len + ") " + id);
}
} catch (final IllegalArgumentException e) {
throw new InvalidGuidOperationException(
"Attempted to parse malformed GUID: " + id, e);
}
return this;
}
@Override
@JsonIgnore
public final int getVersion() {
return guid[HEADER_POS] & 0xFF;
}
@Override
@JsonIgnore
public final int getObjectId() {
return guid[HEADER_POS + 1] & 0xFF;
}
@Override
@JsonIgnore
public final int getTenantId() {
return (guid[TENANT_POS] & 0x3F) << BYTE_SIZE * 3 | (guid[TENANT_POS + 1] & 0xFF) << BYTE_SIZE * 2 |
(guid[TENANT_POS + 2] & 0xFF) << BYTE_SIZE | guid[TENANT_POS + 3] & 0xFF;
}
@Override
@JsonIgnore
public final boolean isWorm() {
return (guid[PLATFORM_POS] & 0x80) != 0;
}
@Override
@JsonIgnore
public final int getPlatformId() {
return (guid[PLATFORM_POS] & 0x7F) << BYTE_SIZE * 3 | (guid[PLATFORM_POS + 1] & 0xFF) << BYTE_SIZE * 2 |
(guid[PLATFORM_POS + 2] & 0xFF) << BYTE_SIZE | guid[PLATFORM_POS + 3] & 0xFF;
}
@Override
@JsonIgnore
public final byte[] getMacFragment() {
if (getVersion() != VERSION) {
return SingletonUtils.getSingletonByteArray();
}
final byte[] x = new byte[6];
x[0] = 0;
x[1] = 0;
x[2] = (byte) (guid[PLATFORM_POS] & 0x7F);
x[3] = guid[PLATFORM_POS + 1];
x[4] = guid[PLATFORM_POS + 2];
x[5] = guid[PLATFORM_POS + 3];
return x;
}
@Override
@JsonIgnore
public final int getProcessId() {
if (getVersion() != VERSION) {
return -1;
}
return (guid[PID_POS] & 0xFF) << BYTE_SIZE * 2 | (guid[PID_POS + 1] & 0xFF) << BYTE_SIZE |
guid[PID_POS + 2] & 0xFF;
}
@Override
@JsonIgnore
public final long getTimestamp() {
if (getVersion() != VERSION) {
return -1;
}
long time = 0;
for (int i = 0; i < TIME_SIZE; i++) {
time <<= BYTE_SIZE;
time |= guid[TIME_POS + i] & 0xFF;
}
return time;
}
@Override
@JsonIgnore
public final int getCounter() {
return (guid[COUNTER_POS] & 0xFF) << BYTE_SIZE * 2 |
(guid[COUNTER_POS + 1] & 0xFF) << BYTE_SIZE |
guid[COUNTER_POS + 2] & 0xFF;
}
@Override
@JsonIgnore
public final String toArkName() {
final byte[] temp = new byte[KEYSIZE - TENANT_SIZE];
System.arraycopy(guid, HEADER_POS, temp, 0, HEADER_SIZE);
System.arraycopy(guid, PLATFORM_POS, temp, HEADER_SIZE,
PLATFORM_SIZE + PID_SIZE + TIME_SIZE + COUNTER_SIZE);
return BaseXx.getBase32(temp);
}
@Override
public int compareTo(final GUID arg1) {
int id = getTenantId();
int id2 = arg1.getTenantId();
if (id != id2) {
return id < id2 ? -1 : 1;
}
id = getObjectId();
id2 = arg1.getObjectId();
if (id != id2) {
return id < id2 ? -1 : 1;
}
final long ts = getTimestamp();
final long ts2 = arg1.getTimestamp();
if (ts == ts2) {
final int ct = getCounter();
final int ct2 = arg1.getCounter();
if (ct == ct2) {
// then all must be equals, else whatever
return Arrays.equals(guid, arg1.getBytes()) ? 0 : -1;
}
// Cannot be equal
return ct < ct2 ? -1 : 1;
}
// others as ProcessId or Platform are unimportant in comparison
return ts < ts2 ? -1 : 1;
}
}