Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Avoid topic filter stackoverflow & add topic tree compaction #338

Merged
merged 10 commits into from
Nov 13, 2019
Original file line number Diff line number Diff line change
@@ -0,0 +1,184 @@
/*
* Copyright 2018 dc-square and the HiveMQ MQTT Client Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/

package com.hivemq.client.internal.mqtt.datatypes;

import com.hivemq.client.internal.util.ByteArrayUtil;
import com.hivemq.client.mqtt.datatypes.MqttTopic;
import com.hivemq.client.mqtt.datatypes.MqttTopicFilter;
import org.jetbrains.annotations.NotNull;

import java.util.Arrays;
import java.util.NoSuchElementException;

/**
* Iterator for a topic or topic filter.
* <p>
* equals and hashCode match the current level.
*
* @author Silvio Giebl
*/
public class MqttTopicIterator extends MqttTopicLevel {

public static @NotNull MqttTopicIterator of(final @NotNull MqttTopicImpl topic) {
final byte[] binary = topic.toBinary();
return new MqttTopicIterator(binary, -1, -1, binary.length);
}

public static @NotNull MqttTopicIterator of(final @NotNull MqttTopicFilterImpl topicFilter) {
final byte[] binary = topicFilter.toBinary();
final int start = topicFilter.getFilterByteStart() - 1;
return new MqttTopicIterator(
binary, start, start, topicFilter.containsMultiLevelWildcard() ? (binary.length - 2) : binary.length);
}

private int start;
private int end;
private final int allEnd;

private MqttTopicIterator(final @NotNull byte[] array, final int start, final int end, final int allEnd) {
super(array);
this.start = start;
this.end = end;
this.allEnd = allEnd;
}

@Override
protected int getStart() {
return start;
}

@Override
protected int getEnd() {
return end;
}

public boolean hasNext() {
return end != allEnd;
}

public boolean hasMultiLevelWildcard() {
return allEnd != array.length;
}

public @NotNull MqttTopicIterator fork() {
return new MqttTopicIterator(array, start, end, allEnd);
}

public @NotNull MqttTopicLevel next() {
if (!hasNext()) {
throw new NoSuchElementException();
}
start = end + 1;
end = ByteArrayUtil.indexOf(array, start, (byte) MqttTopic.TOPIC_LEVEL_SEPARATOR);
return this;
}

@Override
public @NotNull MqttTopicLevel trim() {
if (!hasNext()) {
return MqttTopicLevel.of(array, start, end);
}
final int start = this.start;
final int end = this.end;
this.start = this.end = allEnd;
return new MqttTopicLevels(Arrays.copyOfRange(array, start, allEnd), end - start);
}

public boolean forwardIfEqual(final @NotNull MqttTopicLevels levels) {
final byte[] levelsArray = levels.getArray();
final int levelsEnd = levels.getEnd();
final int to = end + levelsArray.length - levelsEnd;
if ((to <= allEnd) && ((to == allEnd) || (array[to] == MqttTopic.TOPIC_LEVEL_SEPARATOR)) &&
ByteArrayUtil.equals(array, end + 1, to, levelsArray, levelsEnd + 1, levelsArray.length)) {
start = end = to;
return true;
}
return false;
}

public int forwardWhileEqual(final @NotNull MqttTopicLevels levels) {
if (!hasNext()) {
return levels.getEnd();
}
int branchIndex = end;
int levelsBranchIndex = levels.getEnd();
int index = branchIndex + 1;
int levelsIndex = levelsBranchIndex + 1;
final byte[] levelsArray = levels.getArray();
while (true) {
final boolean isEnd = index == allEnd;
final boolean isLevelsEnd = levelsIndex == levelsArray.length;
if (isLevelsEnd || isEnd) {
if ((isLevelsEnd || (levelsArray[levelsIndex] == MqttTopicImpl.TOPIC_LEVEL_SEPARATOR)) &&
(isEnd || (array[index] == MqttTopicImpl.TOPIC_LEVEL_SEPARATOR))) {
branchIndex = index;
levelsBranchIndex = levelsIndex;
}
break;
}
final byte lb = levelsArray[levelsIndex];
if (array[index] == lb) {
if (lb == MqttTopicImpl.TOPIC_LEVEL_SEPARATOR) {
branchIndex = index;
levelsBranchIndex = levelsIndex;
}
index++;
levelsIndex++;
} else {
break;
}
}
start = end = branchIndex;
return levelsBranchIndex;
}

public boolean forwardIfMatch(final @NotNull MqttTopicLevels levels) {
if (!hasNext()) {
return false;
}
int index = end + 1;
int levelsIndex = levels.getEnd() + 1;
final byte[] levelsArray = levels.getArray();
while (true) {
final boolean isEnd = index == allEnd;
final boolean isLevelsEnd = levelsIndex == levelsArray.length;
if (isLevelsEnd) {
if (isEnd || (array[index] == MqttTopicImpl.TOPIC_LEVEL_SEPARATOR)) {
start = end = index;
return true;
}
return false;
}
if (isEnd) {
return false;
}
final byte lb = levelsArray[levelsIndex];
if (array[index] == lb) {
index++;
levelsIndex++;
} else if (lb == MqttTopicFilter.SINGLE_LEVEL_WILDCARD) {
while ((index < allEnd) && (array[index] != MqttTopicImpl.TOPIC_LEVEL_SEPARATOR)) {
index++;
}
levelsIndex++;
} else {
return false;
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,69 +18,46 @@
package com.hivemq.client.internal.mqtt.datatypes;

import com.hivemq.client.internal.util.ByteArray;
import com.hivemq.client.internal.util.ByteArrayUtil;
import com.hivemq.client.mqtt.datatypes.MqttTopic;
import com.hivemq.client.mqtt.datatypes.MqttTopicFilter;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

import java.util.Arrays;

/**
* Single topic or topic filter level. May be the single level wildcard but must not be the multi level wildcard (the
* multi level wildcard does not represent a topic level).
*
* @author Silvio Giebl
*/
public class MqttTopicLevel extends ByteArray.Range {

public static final @NotNull ByteArray SINGLE_LEVEL_WILDCARD =
new ByteArray(new byte[]{MqttTopicFilter.SINGLE_LEVEL_WILDCARD});

public static @NotNull MqttTopicLevel root(final @NotNull MqttTopicImpl topic) {
final byte[] binary = topic.toBinary();
final int end = nextEnd(binary, 0);
return new MqttTopicLevel(binary, 0, end);
}

public static @NotNull MqttTopicLevel root(final @NotNull MqttTopicFilterImpl topicFilter) {
final byte[] binary = topicFilter.toBinary();
final int start = topicFilter.getFilterByteStart();
final int end = nextEnd(binary, start);
return new MqttTopicLevel(binary, start, end);
}
public class MqttTopicLevel extends ByteArray {

private static int nextEnd(final @NotNull byte[] array, final int start) {
final int nextSeparator = ByteArrayUtil.indexOf(array, start, (byte) MqttTopic.TOPIC_LEVEL_SEPARATOR);
return (nextSeparator == -1) ? array.length : nextSeparator;
}
private static final @NotNull MqttTopicLevel SINGLE_LEVEL_WILDCARD =
new MqttTopicLevel(new byte[]{MqttTopicFilter.SINGLE_LEVEL_WILDCARD});

private MqttTopicLevel(final @NotNull byte[] array, final int start, final int end) {
super(array, start, end);
static @NotNull MqttTopicLevel of(final @NotNull byte[] array, final int start, final int end) {
if (isSingleLevelWildcard(array, start, end)) {
return MqttTopicLevel.SINGLE_LEVEL_WILDCARD;
}
return new MqttTopicLevel(Arrays.copyOfRange(array, start, end));
}

public @Nullable MqttTopicLevel next() {
if (end == array.length) {
return null;
}
start = end + 1;
end = nextEnd(array, start);
return this;
private static boolean isSingleLevelWildcard(final @NotNull byte[] array, final int start, final int end) {
return ((end - start) == 1) && (array[start] == MqttTopicFilter.SINGLE_LEVEL_WILDCARD);
}

public @NotNull ByteArray copy() {
if (isSingleLevelWildcard()) {
return SINGLE_LEVEL_WILDCARD;
}
return new ByteArray(Arrays.copyOfRange(array, start, end));
MqttTopicLevel(final @NotNull byte[] array) {
super(array);
}

public @NotNull MqttTopicLevel fork() {
return new MqttTopicLevel(array, start, end);
@NotNull byte[] getArray() {
return array;
}

public boolean isSingleLevelWildcard() {
return (length() == 1) && (array[start] == MqttTopicFilter.SINGLE_LEVEL_WILDCARD);
return isSingleLevelWildcard(array, getStart(), getEnd());
}

public boolean isMultiLevelWildcard() {
return (length() == 1) && (array[start] == MqttTopicFilter.MULTI_LEVEL_WILDCARD);
public @NotNull MqttTopicLevel trim() {
return this;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
/*
* Copyright 2018 dc-square and the HiveMQ MQTT Client Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/

package com.hivemq.client.internal.mqtt.datatypes;

import com.hivemq.client.internal.util.ByteArrayUtil;
import com.hivemq.client.mqtt.datatypes.MqttTopic;
import org.jetbrains.annotations.NotNull;

import java.util.Arrays;

/**
* Multiple (more than 1) topic or topic filter levels. May contain single level wildcard(s) but must not contain the
* multi level wildcard (the multi level wildcard does not represent a topic level).
* <p>
* equals and hashCode match the first level.
*
* @author Silvio Giebl
*/
public class MqttTopicLevels extends MqttTopicLevel {

public static @NotNull MqttTopicLevels concat(
final @NotNull MqttTopicLevel level1, final @NotNull MqttTopicLevel level2) {

final byte[] array1 = level1.trim().getArray();
final byte[] array2 = level2.trim().getArray();
final byte[] array = new byte[array1.length + 1 + array2.length];
System.arraycopy(array1, 0, array, 0, array1.length);
array[array1.length] = MqttTopic.TOPIC_LEVEL_SEPARATOR;
System.arraycopy(array2, 0, array, array1.length + 1, array2.length);
return new MqttTopicLevels(array, level1.length());
}

private final int firstEnd;

MqttTopicLevels(final @NotNull byte[] array, final int firstEnd) {
super(array);
this.firstEnd = firstEnd;
}

@Override
protected int getEnd() {
return firstEnd;
}

public @NotNull MqttTopicLevel before(final int index) {
if (index == array.length) {
return this;
}
assert array[index] == MqttTopic.TOPIC_LEVEL_SEPARATOR;
if (index == firstEnd) {
return MqttTopicLevel.of(array, 0, firstEnd);
}
return new MqttTopicLevels(Arrays.copyOfRange(array, 0, index), firstEnd);
}

public @NotNull MqttTopicLevel after(final int index) {
assert array[index] == MqttTopic.TOPIC_LEVEL_SEPARATOR;
final int start = index + 1;
final int end = ByteArrayUtil.indexOf(array, start, (byte) MqttTopic.TOPIC_LEVEL_SEPARATOR);
if (end == array.length) {
return MqttTopicLevel.of(array, start, array.length);
}
return new MqttTopicLevels(Arrays.copyOfRange(array, start, array.length), end - start);
}
}
Loading