com.hivemq.migration.persistence.legacy.serializer.ClientQueuePersistenceSerializer_4_4 Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of hivemq-community-edition-embedded Show documentation
Show all versions of hivemq-community-edition-embedded Show documentation
HiveMQ CE is a Java-based open source MQTT broker that fully supports MQTT 3.x and MQTT 5
The newest version!
/*
* Copyright 2019-present HiveMQ GmbH
*
* 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.migration.persistence.legacy.serializer;
import com.google.common.primitives.ImmutableIntArray;
import com.hivemq.codec.encoder.mqtt5.Mqtt5PayloadFormatIndicator;
import com.hivemq.extension.sdk.api.annotations.NotNull;
import com.hivemq.extension.sdk.api.annotations.Nullable;
import com.hivemq.migration.persistence.legacy.PUBLISH_4_4;
import com.hivemq.mqtt.message.MessageWithID;
import com.hivemq.mqtt.message.QoS;
import com.hivemq.mqtt.message.mqtt5.Mqtt5UserProperties;
import com.hivemq.mqtt.message.mqtt5.PropertiesSerializationUtil;
import com.hivemq.mqtt.message.publish.PUBLISH;
import com.hivemq.mqtt.message.publish.PUBLISHFactory;
import com.hivemq.mqtt.message.pubrel.PUBREL;
import com.hivemq.persistence.local.xodus.XodusUtils;
import com.hivemq.util.Bytes;
import jetbrains.exodus.ByteIterable;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.concurrent.atomic.AtomicLong;
import static com.hivemq.persistence.clientqueue.ClientQueuePersistenceImpl.Key;
import static java.nio.charset.StandardCharsets.UTF_8;
public class ClientQueuePersistenceSerializer_4_4 {
private static final Logger LOGGER = LoggerFactory.getLogger(ClientQueuePersistenceSerializer_4_4.class);
static final int NO_PACKET_ID = 0;
static final int CLIENT_ID_MATCH = 0;
static final int CLIENT_ID_SAME_PREFIX = 1;
static final int CLIENT_ID_NO_MATCH = 2;
private static final byte PUBLISH_BIT = (byte) 0b1000_0000;
private static final byte PUBREL_BIT = (byte) 0b0100_0000;
private static final byte RETAINED_BIT = (byte) 0b0010_0000;
private static final byte DUPLICATE_DELIVERY_BIT = (byte) 0b0001_0000;
//This bit is only set if the messages was sent as a result of a subscribe messages, regardless of the retain as published option.
private static final byte RETAINED_MESSAGE_BIT = (byte) 0b0000_0100;
private static final byte QOS_BITS = (byte) 0b0000_0011;
private static final byte RESPONSE_TOPIC_PRESENT_BIT = (byte) 0b1000_0000;
private static final byte CONTENT_TYPE_PRESENT_BIT = (byte) 0b0100_0000;
private static final byte CORRELATION_DATA_PRESENT_BIT = (byte) 0b0010_0000;
private static final byte SUBSCRIPTION_IDENTIFIERS_PRESENT_BIT = (byte) 0b0001_0000;
private static final byte USER_PROPERTIES_PRESENT_BIT = (byte) 0b0000_1000;
// The messages must preserve the order in which they are added to the persistence
// ID's < Long.MAX_VALUE / 2 are reserved for messages that should be polled with priority
public static final AtomicLong NEXT_PUBLISH_NUMBER = new AtomicLong(Long.MAX_VALUE / 2);
// ********** Key **********
/**
* Serializes the client id and adds a entry number to represent the message order.
*
* @param key to be serialized
* @return the serialized key for storing a new PUBLISH
*/
@NotNull
public ByteIterable serializeNewPublishKey(@NotNull final Key key) {
return serializeKey(key, NEXT_PUBLISH_NUMBER.getAndIncrement());
}
/**
* Serializes the client id and adds a entry number to represent the message order.
*
* @param key to be serialized
* @return the serialized key for storing an unknown PUBREL
*/
@NotNull
public ByteIterable serializeUnknownPubRelKey(@NotNull final Key key) {
// Ensure unknown PUBRELs are always first
final long messageNumber = NEXT_PUBLISH_NUMBER.getAndIncrement() - Long.MAX_VALUE / 2;
return serializeKey(key, messageNumber);
}
@NotNull
private ByteIterable serializeKey(@NotNull final Key key, final long number) {
final byte[] clientBytes = key.getQueueId().getBytes(UTF_8);
final byte[] result = new byte[clientBytes.length + 1 + Long.BYTES];
System.arraycopy(clientBytes, 0, result, 0, clientBytes.length);
result[clientBytes.length] = (byte) (key.isShared() ? 1 : 0);
Bytes.copyLongToByteArray(number, result, clientBytes.length + 1);
return XodusUtils.bytesToByteIterable(result);
}
/**
* Serializes the client id for searching.
*
* @param key to be serialized
* @return the serialized client id for searching
*/
@NotNull
public ByteIterable serializeKey(@NotNull final Key key) {
final byte[] clientBytes = key.getQueueId().getBytes(UTF_8);
final byte[] result = new byte[clientBytes.length + 1];
System.arraycopy(clientBytes, 0, result, 0, clientBytes.length);
result[clientBytes.length] = (byte) (key.isShared() ? 1 : 0);
return XodusUtils.bytesToByteIterable(result);
}
int compareClientId(@NotNull final ByteIterable serializedClientId, @NotNull final ByteIterable serializedKey) {
final int clientLength = serializedClientId.getLength();
if (serializedClientId.compareTo(serializedKey.subIterable(0, clientLength)) != 0) {
return CLIENT_ID_NO_MATCH;
}
if (clientLength == (serializedKey.getLength() - Long.BYTES)) {
return CLIENT_ID_MATCH;
}
return CLIENT_ID_SAME_PREFIX;
}
@NotNull
public Key deserializeKeyId(@NotNull final ByteIterable serializedKey) {
final byte[] bytes = serializedKey.getBytesUnsafe();
final int clientIdLength = serializedKey.getLength() - 1 - Long.BYTES;
final String client = new String(bytes, 0, clientIdLength, UTF_8);
final boolean shared = bytes[clientIdLength] == 1;
return new Key(client, shared);
}
// ********** Value **********
@NotNull
public ByteIterable serializePublishWithoutPacketId(@NotNull final PUBLISH_4_4 publish, final boolean retained) {
return XodusUtils.bytesToByteIterable(createPublishBytes(publish, retained));
}
@NotNull
public ByteIterable serializeAndSetPacketId(@NotNull final ByteIterable serializedValue, final int packetId) {
final byte[] bytes = XodusUtils.byteIterableToBytes(serializedValue);
Bytes.copyUnsignedShortToByteArray(packetId, bytes, 0);
return XodusUtils.bytesToByteIterable(bytes);
}
public int deserializePacketId(@NotNull final ByteIterable serializedValue) {
return Bytes.readUnsignedShort(serializedValue.getBytesUnsafe(), 0);
}
@NotNull
public MessageWithID deserializeValue(@NotNull final ByteIterable serializedValue) {
final byte[] bytes = serializedValue.getBytesUnsafe();
if ((bytes[Short.BYTES] & PUBREL_BIT) == PUBREL_BIT) {
final int packetId = Bytes.readUnsignedShort(bytes, 0);
final PUBREL pubrel = new PUBREL(packetId);
if (serializedValue.getLength() >= Short.BYTES + 1 + Long.BYTES * 2) {
final long expiry = Bytes.readLong(bytes, Short.BYTES + 1);
pubrel.setMessageExpiryInterval(expiry);
final long timestamp = Bytes.readLong(bytes, Short.BYTES + 1 + Long.BYTES);
pubrel.setPublishTimestamp(timestamp);
}
return pubrel;
}
if ((bytes[Short.BYTES] & PUBLISH_BIT) == PUBLISH_BIT) {
return deserializePublish(bytes);
}
LOGGER.error("Could not deserialize client queue persistence value");
throw new IllegalArgumentException("Invalid client queue persistence value to deserialize");
}
public boolean deserializeRetained(@NotNull final ByteIterable serializedValue) {
final byte[] bytes = serializedValue.getBytesUnsafe();
return (bytes[Short.BYTES] & RETAINED_MESSAGE_BIT) == RETAINED_MESSAGE_BIT;
}
@NotNull
private byte[] createPubrelBytes(
final int packetId,
final boolean retained,
@Nullable final Long expiry,
@Nullable final Long publishTimestamp) {
final byte[] result;
if (expiry != null && publishTimestamp != null) {
result = new byte[Short.BYTES + 1 + Long.BYTES * 2];
} else {
result = new byte[Short.BYTES + 1];
}
int cursor = 0;
cursor = XodusUtils.serializeShort(packetId, result, cursor);
result[cursor] = PUBREL_BIT;
if (retained) {
result[cursor] |= RETAINED_MESSAGE_BIT;
}
cursor++;
if (expiry != null && publishTimestamp != null) {
cursor = XodusUtils.serializeLong(expiry, result, cursor);
cursor = XodusUtils.serializeLong(publishTimestamp, result, cursor);
}
return result;
}
@NotNull
private byte[] createPublishBytes(@NotNull final PUBLISH_4_4 legacyPub, final boolean retained) {
final PUBLISH message = legacyPub.getPublish();
final byte[] topic = message.getTopic().getBytes(UTF_8);
final byte[] hivemqId = message.getHivemqId().getBytes(UTF_8);
final byte[] responseTopic =
message.getResponseTopic() == null ? null : message.getResponseTopic().getBytes(UTF_8);
final byte[] contentType = message.getContentType() == null ? null : message.getContentType().getBytes(UTF_8);
final byte[] correlationData = message.getCorrelationData();
final ImmutableIntArray subscriptionIdentifiers = message.getSubscriptionIdentifiers();
final int subscriptionIdentifierLength = subscriptionIdentifiers == null ? 0 : subscriptionIdentifiers.length();
final int payloadFormatIndicator =
message.getPayloadFormatIndicator() != null ? message.getPayloadFormatIndicator().getCode() : -1;
final Mqtt5UserProperties userProperties = message.getUserProperties();
final byte[] result = new byte[Short.BYTES +
// packet id
1 +
// PUBLISH_BIT, dup, retain, qos
1 +
// present flags
XodusUtils.shortLengthArraySize(topic) +
// topic
Long.BYTES +
// timestamp
Long.BYTES +
// publish id
XodusUtils.shortLengthArraySize(hivemqId) +
// hivemq id
Long.BYTES +
// payload id
Long.BYTES +
// message expiry
(responseTopic == null ? 0 : XodusUtils.shortLengthArraySize(responseTopic)) +
// response topic
(contentType == null ? 0 : XodusUtils.shortLengthArraySize(contentType)) +
// content type
(correlationData == null ? 0 : XodusUtils.shortLengthArraySize(correlationData)) +
// correlation data
(subscriptionIdentifiers == null ? 0 : Integer.BYTES + subscriptionIdentifierLength * Integer.BYTES) +
// subscription identifiers
1 +
// payload format indicator
(userProperties.asList().size() == 0 ? 0 : PropertiesSerializationUtil.encodedSize(userProperties))];
int cursor = 0;
cursor = XodusUtils.serializeShort(NO_PACKET_ID, result, cursor);
byte flags = PUBLISH_BIT;
flags |= message.getQoS().getQosNumber();
if (message.isDuplicateDelivery()) {
flags |= DUPLICATE_DELIVERY_BIT;
}
if (message.isRetain()) {
flags |= RETAINED_BIT;
}
if (retained) {
flags |= RETAINED_MESSAGE_BIT;
}
cursor = XodusUtils.serializeByte(flags, result, cursor);
byte presentFlags = (byte) 0b0000_0000;
if (responseTopic != null) {
presentFlags |= RESPONSE_TOPIC_PRESENT_BIT;
}
if (contentType != null) {
presentFlags |= CONTENT_TYPE_PRESENT_BIT;
}
if (correlationData != null) {
presentFlags |= CORRELATION_DATA_PRESENT_BIT;
}
if (subscriptionIdentifiers != null) {
presentFlags |= SUBSCRIPTION_IDENTIFIERS_PRESENT_BIT;
}
if (userProperties.asList().size() > 0) {
presentFlags |= USER_PROPERTIES_PRESENT_BIT;
}
cursor = XodusUtils.serializeByte(presentFlags, result, cursor);
cursor = XodusUtils.serializeShortLengthArray(topic, result, cursor);
cursor = XodusUtils.serializeLong(message.getTimestamp(), result, cursor);
cursor = XodusUtils.serializeLong(message.getPublishId(), result, cursor);
cursor = XodusUtils.serializeShortLengthArray(hivemqId, result, cursor);
cursor = XodusUtils.serializeLong(legacyPub.getPayloadID(), result, cursor);
cursor = XodusUtils.serializeLong(message.getMessageExpiryInterval(), result, cursor);
if (responseTopic != null) {
cursor = XodusUtils.serializeShortLengthArray(responseTopic, result, cursor);
}
if (contentType != null) {
cursor = XodusUtils.serializeShortLengthArray(contentType, result, cursor);
}
if (correlationData != null) {
cursor = XodusUtils.serializeShortLengthArray(correlationData, result, cursor);
}
if (subscriptionIdentifiers != null) {
Bytes.copyIntToByteArray(subscriptionIdentifierLength, result, cursor);
cursor += Integer.BYTES;
if (subscriptionIdentifierLength > 0) {
for (int i = 0; i < subscriptionIdentifiers.length(); i++) {
Bytes.copyIntToByteArray(subscriptionIdentifiers.get(i), result, cursor);
cursor += Integer.BYTES;
}
}
}
cursor = XodusUtils.serializeByte((byte) payloadFormatIndicator, result, cursor);
if (userProperties.asList().size() > 0) {
PropertiesSerializationUtil.write(userProperties, result, cursor);
}
return result;
}
@NotNull
private PUBLISH_4_4 deserializePublish(@NotNull final byte[] serialized) {
final PUBLISHFactory.Mqtt5Builder builder = new PUBLISHFactory.Mqtt5Builder();
int cursor = 0;
builder.withPacketIdentifier(Bytes.readUnsignedShort(serialized, cursor));
cursor += Short.BYTES;
builder.withQoS(QoS.valueOf(serialized[cursor] & QOS_BITS));
builder.withOnwardQos(QoS.valueOf(serialized[cursor] & QOS_BITS));
builder.withDuplicateDelivery((serialized[cursor] & DUPLICATE_DELIVERY_BIT) == DUPLICATE_DELIVERY_BIT);
builder.withRetain((serialized[cursor] & RETAINED_BIT) == RETAINED_BIT);
cursor += 1;
final boolean responseTopicPresent =
(serialized[cursor] & RESPONSE_TOPIC_PRESENT_BIT) == RESPONSE_TOPIC_PRESENT_BIT;
final boolean contentTypePresent = (serialized[cursor] & CONTENT_TYPE_PRESENT_BIT) == CONTENT_TYPE_PRESENT_BIT;
final boolean correlationDataPresent =
(serialized[cursor] & CORRELATION_DATA_PRESENT_BIT) == CORRELATION_DATA_PRESENT_BIT;
final boolean subscriptionIndetifiersPresent =
(serialized[cursor] & SUBSCRIPTION_IDENTIFIERS_PRESENT_BIT) == SUBSCRIPTION_IDENTIFIERS_PRESENT_BIT;
final boolean userPropertiesPresent =
(serialized[cursor] & USER_PROPERTIES_PRESENT_BIT) == USER_PROPERTIES_PRESENT_BIT;
cursor += 1;
final int topicLength = Bytes.readUnsignedShort(serialized, cursor);
cursor += Short.BYTES;
builder.withTopic(new String(serialized, cursor, topicLength, UTF_8));
cursor += topicLength;
builder.withTimestamp(Bytes.readLong(serialized, cursor));
cursor += Long.BYTES;
builder.withPublishId(Bytes.readLong(serialized, cursor));
cursor += Long.BYTES;
final int hivemqIdLength = Bytes.readUnsignedShort(serialized, cursor);
cursor += Short.BYTES;
builder.withHivemqId(new String(serialized, cursor, hivemqIdLength, UTF_8));
cursor += hivemqIdLength;
long payloadId = Bytes.readLong(serialized, cursor);
cursor += Long.BYTES;
builder.withMessageExpiryInterval(Bytes.readLong(serialized, cursor));
cursor += Long.BYTES;
if (responseTopicPresent) {
final int responseTopicLength = Bytes.readUnsignedShort(serialized, cursor);
cursor += Short.BYTES;
if (responseTopicLength != 0) {
builder.withResponseTopic(new String(serialized, cursor, responseTopicLength, UTF_8));
cursor += responseTopicLength;
}
}
if (contentTypePresent) {
final int contentTypeLength = Bytes.readUnsignedShort(serialized, cursor);
cursor += Short.BYTES;
if (contentTypeLength != 0) {
builder.withContentType(new String(serialized, cursor, contentTypeLength, UTF_8));
cursor += contentTypeLength;
}
}
if (correlationDataPresent) {
final int correlationDataLength = Bytes.readUnsignedShort(serialized, cursor);
cursor += Short.BYTES;
if (correlationDataLength != 0) {
final byte[] correlationData = new byte[correlationDataLength];
System.arraycopy(serialized, cursor, correlationData, 0, correlationDataLength);
builder.withCorrelationData(correlationData);
cursor += correlationDataLength;
}
}
if (subscriptionIndetifiersPresent) {
final int subscriptionIdentifiersLength = Bytes.readInt(serialized, cursor);
cursor += Integer.BYTES;
final ImmutableIntArray.Builder subscriptionIdentifiers = ImmutableIntArray.builder();
for (int i = 0; i < subscriptionIdentifiersLength; i++) {
subscriptionIdentifiers.add(Bytes.readInt(serialized, cursor));
cursor += Integer.BYTES;
}
builder.withSubscriptionIdentifiers(subscriptionIdentifiers.build());
}
builder.withPayloadFormatIndicator(Mqtt5PayloadFormatIndicator.fromCode(serialized[cursor]));
cursor += 1;
if (userPropertiesPresent) {
builder.withUserProperties(PropertiesSerializationUtil.read(serialized, cursor));
}
return new PUBLISH_4_4(builder.build(), payloadId);
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy