From 2879d869c19d9058e69e2f1db67d537c394f918b Mon Sep 17 00:00:00 2001 From: Simon Brandhof Date: Thu, 27 Aug 2015 15:36:03 +0200 Subject: SONAR-6812 Improve format of generated UUIDs for better usage of Lucene --- .../org/sonar/core/issue/DefaultActionPlan.java | 2 +- .../org/sonar/core/issue/DefaultIssueBuilder.java | 2 +- .../org/sonar/core/issue/DefaultIssueComment.java | 2 +- .../org/sonar/core/platform/ComponentKeys.java | 2 +- .../org/sonar/core/util/MacAddressProvider.java | 108 +++++++++++++++++++++ .../main/java/org/sonar/core/util/UuidFactory.java | 34 +++++++ .../java/org/sonar/core/util/UuidFactoryImpl.java | 87 +++++++++++++++++ .../src/main/java/org/sonar/core/util/Uuids.java | 36 +++++++ .../sonar/core/util/MacAddressProviderTest.java | 41 ++++++++ .../org/sonar/core/util/UuidFactoryImplTest.java | 46 +++++++++ .../test/java/org/sonar/core/util/UuidsTest.java | 47 +++++++++ 11 files changed, 403 insertions(+), 4 deletions(-) create mode 100644 sonar-core/src/main/java/org/sonar/core/util/MacAddressProvider.java create mode 100644 sonar-core/src/main/java/org/sonar/core/util/UuidFactory.java create mode 100644 sonar-core/src/main/java/org/sonar/core/util/UuidFactoryImpl.java create mode 100644 sonar-core/src/main/java/org/sonar/core/util/Uuids.java create mode 100644 sonar-core/src/test/java/org/sonar/core/util/MacAddressProviderTest.java create mode 100644 sonar-core/src/test/java/org/sonar/core/util/UuidFactoryImplTest.java create mode 100644 sonar-core/src/test/java/org/sonar/core/util/UuidsTest.java (limited to 'sonar-core') diff --git a/sonar-core/src/main/java/org/sonar/core/issue/DefaultActionPlan.java b/sonar-core/src/main/java/org/sonar/core/issue/DefaultActionPlan.java index 3e8fbdded69..14c0ab60597 100644 --- a/sonar-core/src/main/java/org/sonar/core/issue/DefaultActionPlan.java +++ b/sonar-core/src/main/java/org/sonar/core/issue/DefaultActionPlan.java @@ -24,7 +24,7 @@ import java.util.Date; import javax.annotation.CheckForNull; import javax.annotation.Nullable; import org.sonar.api.issue.ActionPlan; -import org.sonar.api.utils.internal.Uuids; +import org.sonar.core.util.Uuids; public class DefaultActionPlan implements ActionPlan { diff --git a/sonar-core/src/main/java/org/sonar/core/issue/DefaultIssueBuilder.java b/sonar-core/src/main/java/org/sonar/core/issue/DefaultIssueBuilder.java index e65a71bab69..77c6237f3d7 100644 --- a/sonar-core/src/main/java/org/sonar/core/issue/DefaultIssueBuilder.java +++ b/sonar-core/src/main/java/org/sonar/core/issue/DefaultIssueBuilder.java @@ -28,7 +28,7 @@ import org.sonar.api.issue.Issuable; import org.sonar.api.issue.Issuable.IssueBuilder; import org.sonar.api.issue.Issue; import org.sonar.api.rule.RuleKey; -import org.sonar.api.utils.internal.Uuids; +import org.sonar.core.util.Uuids; public class DefaultIssueBuilder implements Issuable.IssueBuilder { diff --git a/sonar-core/src/main/java/org/sonar/core/issue/DefaultIssueComment.java b/sonar-core/src/main/java/org/sonar/core/issue/DefaultIssueComment.java index c54e50546ee..f13137370b7 100644 --- a/sonar-core/src/main/java/org/sonar/core/issue/DefaultIssueComment.java +++ b/sonar-core/src/main/java/org/sonar/core/issue/DefaultIssueComment.java @@ -24,7 +24,7 @@ import java.util.Date; import javax.annotation.CheckForNull; import javax.annotation.Nullable; import org.sonar.api.issue.IssueComment; -import org.sonar.api.utils.internal.Uuids; +import org.sonar.core.util.Uuids; /** * PLUGINS MUST NOT BE USED THIS CLASS, EXCEPT FOR UNIT TESTING. diff --git a/sonar-core/src/main/java/org/sonar/core/platform/ComponentKeys.java b/sonar-core/src/main/java/org/sonar/core/platform/ComponentKeys.java index 1e02781c64f..94978b7ae49 100644 --- a/sonar-core/src/main/java/org/sonar/core/platform/ComponentKeys.java +++ b/sonar-core/src/main/java/org/sonar/core/platform/ComponentKeys.java @@ -22,7 +22,7 @@ package org.sonar.core.platform; import java.util.HashSet; import java.util.Set; import java.util.regex.Pattern; -import org.sonar.api.utils.internal.Uuids; +import org.sonar.core.util.Uuids; import org.sonar.api.utils.log.Logger; import org.sonar.api.utils.log.Loggers; diff --git a/sonar-core/src/main/java/org/sonar/core/util/MacAddressProvider.java b/sonar-core/src/main/java/org/sonar/core/util/MacAddressProvider.java new file mode 100644 index 00000000000..8b78b583045 --- /dev/null +++ b/sonar-core/src/main/java/org/sonar/core/util/MacAddressProvider.java @@ -0,0 +1,108 @@ +/* + * SonarQube, open source software quality management tool. + * Copyright (C) 2008-2014 SonarSource + * mailto:contact AT sonarsource DOT com + * + * SonarQube is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * SonarQube is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.core.util; + +import com.google.common.annotations.VisibleForTesting; +import java.net.NetworkInterface; +import java.net.SocketException; +import java.security.SecureRandom; +import java.util.Enumeration; +import javax.annotation.CheckForNull; +import javax.annotation.Nullable; +import org.sonar.api.utils.log.Logger; +import org.sonar.api.utils.log.Loggers; + +/** + * Used by {@link UuidFactoryImpl}. Heavily inspired by https://github.com/elastic/elasticsearch/blob/master/core/src/main/java/org/elasticsearch/common/MacAddressProvider.java + */ +class MacAddressProvider { + + private static final Logger LOGGER = Loggers.get(MacAddressProvider.class); + public static final int BYTE_SIZE = 6; + + private MacAddressProvider() { + // only static stuff + } + + public static byte[] getSecureMungedAddress() { + byte[] address = null; + try { + address = getMacAddress(); + } catch (SocketException se) { + LOGGER.warn("Unable to get mac address, will use a dummy address", se); + // address will be set below + } + + if (!isValidAddress(address)) { + LOGGER.warn("Unable to get a valid mac address, will use a dummy address"); + address = constructDummyMulticastAddress(); + } + + byte[] mungedBytes = new byte[BYTE_SIZE]; + new SecureRandom().nextBytes(mungedBytes); + for (int i = 0; i < BYTE_SIZE; ++i) { + mungedBytes[i] ^= address[i]; + } + + return mungedBytes; + } + + private static boolean isValidAddress(@Nullable byte[] address) { + if (address == null || address.length != BYTE_SIZE) { + return false; + } + for (byte b : address) { + if (b != 0x00) { + // If any of the bytes are non zero assume a good address + return true; + } + } + return false; + } + + @CheckForNull + private static byte[] getMacAddress() throws SocketException { + Enumeration en = NetworkInterface.getNetworkInterfaces(); + if (en != null) { + while (en.hasMoreElements()) { + NetworkInterface nint = en.nextElement(); + if (!nint.isLoopback()) { + // Pick the first valid non loopback address we find + byte[] address = nint.getHardwareAddress(); + if (isValidAddress(address)) { + return address; + } + } + } + } + // Could not find a mac address + return null; + } + + @VisibleForTesting + static byte[] constructDummyMulticastAddress() { + byte[] dummy = new byte[BYTE_SIZE]; + new SecureRandom().nextBytes(dummy); + // Set the broadcast bit to indicate this is not a _real_ mac address + dummy[0] |= (byte) 0x01; + return dummy; + } + +} diff --git a/sonar-core/src/main/java/org/sonar/core/util/UuidFactory.java b/sonar-core/src/main/java/org/sonar/core/util/UuidFactory.java new file mode 100644 index 00000000000..66ca3fbeeb3 --- /dev/null +++ b/sonar-core/src/main/java/org/sonar/core/util/UuidFactory.java @@ -0,0 +1,34 @@ +/* + * SonarQube, open source software quality management tool. + * Copyright (C) 2008-2014 SonarSource + * mailto:contact AT sonarsource DOT com + * + * SonarQube is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * SonarQube is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.core.util; + +public interface UuidFactory { + + /** + * Create a universally unique identifier. Underlying algorithm, so format and max length, + * can vary over SonarQube versions. + *

+ * UUID is a base64 ASCII encoded string and is URL-safe. It does not contain - and + characters + * but only letters, digits, dash (-) and underscore (_). Length can vary but does + * not exceed 40 characters (arbitrary value). + */ + String create(); + +} diff --git a/sonar-core/src/main/java/org/sonar/core/util/UuidFactoryImpl.java b/sonar-core/src/main/java/org/sonar/core/util/UuidFactoryImpl.java new file mode 100644 index 00000000000..0f0d855c78d --- /dev/null +++ b/sonar-core/src/main/java/org/sonar/core/util/UuidFactoryImpl.java @@ -0,0 +1,87 @@ +/* + * SonarQube, open source software quality management tool. + * Copyright (C) 2008-2014 SonarSource + * mailto:contact AT sonarsource DOT com + * + * SonarQube is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * SonarQube is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.core.util; + +import java.security.SecureRandom; +import java.util.concurrent.atomic.AtomicInteger; +import org.apache.commons.codec.binary.Base64; + +/** + * Heavily inspired from Elasticsearch {@code TimeBasedUUIDGenerator}, which could be directly + * used the day {@code UuidFactoryImpl} is moved outside module sonar-core. + * See https://github.com/elastic/elasticsearch/blob/master/core/src/main/java/org/elasticsearch/common/TimeBasedUUIDGenerator.java + */ +public enum UuidFactoryImpl implements UuidFactory { + + /** + * Should be removed as long {@link Uuids} is not used anymore. {@code UuidFactoryImpl} + * should be built by picocontainer through a public constructor. + */ + INSTANCE; + + // We only use bottom 3 bytes for the sequence number. Paranoia: init with random int so that if JVM/OS/machine goes down, clock slips + // backwards, and JVM comes back up, we are less likely to be on the same sequenceNumber at the same time: + private final AtomicInteger sequenceNumber = new AtomicInteger(new SecureRandom().nextInt()); + + // Used to ensure clock moves forward + private long lastTimestamp = 0L; + + private final byte[] secureMungedAddress = MacAddressProvider.getSecureMungedAddress(); + + @Override + public String create() { + int sequenceId = sequenceNumber.incrementAndGet() & 0xffffff; + long timestamp = System.currentTimeMillis(); + + synchronized (this) { + // Don't let timestamp go backwards, at least "on our watch" (while this JVM is running). We are still vulnerable if we are + // shut down, clock goes backwards, and we restart... for this we randomize the sequenceNumber on init to decrease chance of + // collision: + timestamp = Math.max(lastTimestamp, timestamp); + + if (sequenceId == 0) { + // Always force the clock to increment whenever sequence number is 0, in case we have a long time-slip backwards: + timestamp++; + } + + lastTimestamp = timestamp; + } + + byte[] uuidBytes = new byte[15]; + + // Only use lower 6 bytes of the timestamp (this will suffice beyond the year 10000): + putLong(uuidBytes, timestamp, 0, 6); + + // MAC address adds 6 bytes: + System.arraycopy(secureMungedAddress, 0, uuidBytes, 6, secureMungedAddress.length); + + // Sequence number adds 3 bytes: + putLong(uuidBytes, sequenceId, 12, 3); + + return Base64.encodeBase64URLSafeString(uuidBytes); + } + + /** Puts the lower numberOfLongBytes from l into the array, starting index pos. */ + private static void putLong(byte[] array, long l, int pos, int numberOfLongBytes) { + for (int i = 0; i < numberOfLongBytes; ++i) { + array[pos + numberOfLongBytes - i - 1] = (byte) (l >>> (i * 8)); + } + } +} diff --git a/sonar-core/src/main/java/org/sonar/core/util/Uuids.java b/sonar-core/src/main/java/org/sonar/core/util/Uuids.java new file mode 100644 index 00000000000..387b75e1391 --- /dev/null +++ b/sonar-core/src/main/java/org/sonar/core/util/Uuids.java @@ -0,0 +1,36 @@ +/* + * SonarQube, open source software quality management tool. + * Copyright (C) 2008-2014 SonarSource + * mailto:contact AT sonarsource DOT com + * + * SonarQube is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * SonarQube is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.core.util; + +public class Uuids { + + private Uuids() { + // only static fields + } + + /** + * Create a universally unique identifier. It's recommended to use the non-static way + * through {@link UuidFactory} which is available in IoC container. + * @see UuidFactory#create() + */ + public static String create() { + return UuidFactoryImpl.INSTANCE.create(); + } +} diff --git a/sonar-core/src/test/java/org/sonar/core/util/MacAddressProviderTest.java b/sonar-core/src/test/java/org/sonar/core/util/MacAddressProviderTest.java new file mode 100644 index 00000000000..dc568e9768d --- /dev/null +++ b/sonar-core/src/test/java/org/sonar/core/util/MacAddressProviderTest.java @@ -0,0 +1,41 @@ +/* + * SonarQube, open source software quality management tool. + * Copyright (C) 2008-2014 SonarSource + * mailto:contact AT sonarsource DOT com + * + * SonarQube is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * SonarQube is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.core.util; + +import org.junit.Test; + +import static org.assertj.core.api.Assertions.assertThat; + +public class MacAddressProviderTest { + + @Test + public void getSecureMungedAddress() throws Exception { + byte[] address = MacAddressProvider.getSecureMungedAddress(); + assertThat(address).isNotEmpty(); + assertThat(address).hasSize(6); + } + + @Test + public void constructDummyMulticastAddress() { + byte[] address = MacAddressProvider.constructDummyMulticastAddress(); + assertThat(address).isNotEmpty(); + assertThat(address).hasSize(6); + } +} diff --git a/sonar-core/src/test/java/org/sonar/core/util/UuidFactoryImplTest.java b/sonar-core/src/test/java/org/sonar/core/util/UuidFactoryImplTest.java new file mode 100644 index 00000000000..0774e321738 --- /dev/null +++ b/sonar-core/src/test/java/org/sonar/core/util/UuidFactoryImplTest.java @@ -0,0 +1,46 @@ +/* + * SonarQube, open source software quality management tool. + * Copyright (C) 2008-2014 SonarSource + * mailto:contact AT sonarsource DOT com + * + * SonarQube is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * SonarQube is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.core.util; + +import org.junit.Test; + +import static org.assertj.core.api.Assertions.assertThat; + +public class UuidFactoryImplTest { + + UuidFactory underTest = UuidFactoryImpl.INSTANCE; + + @Test + public void create_different_uuids() { + // this test is not enough to ensure that generated strings are unique, + // but it still does a simple and stupid verification + assertThat(underTest.create()).isNotEqualTo(underTest.create()); + } + + @Test + public void test_format_of_uuid() throws Exception { + String uuid = underTest.create(); + + assertThat(uuid.length()).isGreaterThan(10).isLessThan(40); + + // URL-safe: only letters, digits, dash and underscore. + assertThat(uuid).matches("^[\\w\\-_]+$"); + } +} diff --git a/sonar-core/src/test/java/org/sonar/core/util/UuidsTest.java b/sonar-core/src/test/java/org/sonar/core/util/UuidsTest.java new file mode 100644 index 00000000000..dd3efff9b3a --- /dev/null +++ b/sonar-core/src/test/java/org/sonar/core/util/UuidsTest.java @@ -0,0 +1,47 @@ +/* + * SonarQube, open source software quality management tool. + * Copyright (C) 2008-2014 SonarSource + * mailto:contact AT sonarsource DOT com + * + * SonarQube is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * SonarQube is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.core.util; + +import com.google.common.collect.Sets; +import org.junit.Test; +import org.sonar.test.TestUtils; + +import java.util.Set; + +import static org.assertj.core.api.Assertions.assertThat; + +public class UuidsTest { + + @Test + public void create_unique() { + Set all = Sets.newHashSet(); + for (int i = 0; i < 50; i++) { + String uuid = Uuids.create(); + assertThat(uuid).isNotEmpty(); + all.add(uuid); + } + assertThat(all).hasSize(50); + } + + @Test + public void constructor_is_private() { + TestUtils.hasOnlyPrivateConstructors(Uuids.class); + } +} -- cgit v1.2.3