
com.hivemq.codec.encoder.mqtt5.Mqtt5MessageWithUserPropertiesEncoder 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.codec.encoder.mqtt5;
import com.google.common.base.Preconditions;
import com.hivemq.bootstrap.ClientConnectionContext;
import com.hivemq.codec.encoder.MqttEncoder;
import com.hivemq.configuration.service.SecurityConfigurationService;
import com.hivemq.extension.sdk.api.annotations.NotNull;
import com.hivemq.mqtt.event.PublishDroppedEvent;
import com.hivemq.mqtt.message.Message;
import com.hivemq.mqtt.message.connack.CONNACK;
import com.hivemq.mqtt.message.connect.Mqtt5CONNECT;
import com.hivemq.mqtt.message.disconnect.DISCONNECT;
import com.hivemq.mqtt.message.dropping.MessageDroppedService;
import com.hivemq.mqtt.message.mqtt5.Mqtt5UserProperties;
import com.hivemq.mqtt.message.mqtt5.MqttMessageWithUserProperties;
import com.hivemq.mqtt.message.publish.PUBLISH;
import com.hivemq.mqtt.message.reason.Mqtt5ReasonCode;
import io.netty.buffer.ByteBuf;
import io.netty.handler.codec.EncoderException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.Objects;
import static com.hivemq.codec.encoder.mqtt5.Mqtt5MessageEncoderUtil.encodeNullableProperty;
import static com.hivemq.codec.encoder.mqtt5.Mqtt5MessageEncoderUtil.nullablePropertyEncodedLength;
import static com.hivemq.codec.encoder.mqtt5.MqttMessageEncoderUtil.encodedLengthWithHeader;
import static com.hivemq.codec.encoder.mqtt5.MqttMessageEncoderUtil.encodedPacketLength;
import static com.hivemq.codec.encoder.mqtt5.MqttVariableByteInteger.MAXIMUM_PACKET_SIZE_LIMIT;
import static com.hivemq.mqtt.message.mqtt5.MessageProperties.REASON_STRING;
/**
* Base class for encoders of MQTT messages with omissible User Properties.
*
* @author Silvio Giebl
*/
abstract class Mqtt5MessageWithUserPropertiesEncoder implements MqttEncoder {
private static final Logger log = LoggerFactory.getLogger(Mqtt5MessageWithUserPropertiesEncoder.class);
private final @NotNull MessageDroppedService messageDroppedService;
// Need the security service here for enabling / disabling configuration on runtime.
private final @NotNull SecurityConfigurationService securityConfigurationService;
Mqtt5MessageWithUserPropertiesEncoder(
final @NotNull MessageDroppedService messageDroppedService,
final @NotNull SecurityConfigurationService securityConfigurationService) {
this.messageDroppedService = messageDroppedService;
this.securityConfigurationService = securityConfigurationService;
}
@Override
public void encode(
final @NotNull ClientConnectionContext clientConnectionContext,
final @NotNull T msg,
final @NotNull ByteBuf out) {
Preconditions.checkNotNull(clientConnectionContext, "ClientContext must never be null");
Preconditions.checkNotNull(msg, "Message must never be null");
Preconditions.checkNotNull(out, "ByteBuf must never be null");
if (msg.getOmittedProperties() > 0) {
final String clientIdFromChannel = clientConnectionContext.getClientId();
final String clientId = clientIdFromChannel != null ? clientIdFromChannel : "UNKNOWN";
final long maximumPacketSize = calculateMaxMessageSize(clientConnectionContext);
//PUBLISH must not omit any properties
if (msg instanceof PUBLISH) {
// The maximal packet size exceeds the clients accepted packet size
clientConnectionContext.getChannel()
.pipeline()
.fireUserEventTriggered(new PublishDroppedEvent((PUBLISH) msg));
messageDroppedService.publishMaxPacketSizeExceeded(clientId,
((PUBLISH) msg).getTopic(),
((PUBLISH) msg).getQoS().getQosNumber(),
maximumPacketSize,
msg.getEncodedLength());
if (log.isTraceEnabled()) {
log.trace("Could not encode publish message for client ({}): Maximum packet size limit exceeded",
clientId);
}
return;
}
if (msg.getPropertyLength() < 0 && msg.getEncodedLength() > maximumPacketSize) {
messageDroppedService.messageMaxPacketSizeExceeded(clientId,
msg.getType().name(),
maximumPacketSize,
msg.getEncodedLength());
if (log.isTraceEnabled()) {
log.trace("Could not encode message of type {} for client {}: Packet too large",
msg.getType(),
clientId);
}
throw new EncoderException("Maximum packet size exceeded");
}
}
encode(msg, out);
}
@Override
public int bufferSize(final @NotNull ClientConnectionContext clientConnectionContext, final @NotNull T msg) {
int omittedProperties = 0;
int propertyLength = calculatePropertyLength(msg);
if (!securityConfigurationService.allowRequestProblemInformation() ||
!Objects.requireNonNullElse(clientConnectionContext.getRequestProblemInformation(),
Mqtt5CONNECT.DEFAULT_PROBLEM_INFORMATION_REQUESTED)) {
//Must omit user properties and reason string for any other packet than PUBLISH, CONNACK, DISCONNECT
//if no problem information requested.
final boolean mustOmit = !(msg instanceof PUBLISH || msg instanceof CONNACK || msg instanceof DISCONNECT);
if (mustOmit) {
// propertyLength - reason string
propertyLength = propertyLength(msg, propertyLength, ++omittedProperties);
// propertyLength - user properties
propertyLength = propertyLength(msg, propertyLength, ++omittedProperties);
}
}
final long maximumPacketSize = calculateMaxMessageSize(clientConnectionContext);
final int remainingLengthWithoutProperties = calculateRemainingLengthWithoutProperties(msg);
int remainingLength = remainingLength(msg, remainingLengthWithoutProperties, propertyLength);
int encodedLength = encodedPacketLength(remainingLength);
while (encodedLength > maximumPacketSize) {
omittedProperties++;
propertyLength = propertyLength(msg, propertyLength, omittedProperties);
if (propertyLength == -1) {
break;
}
remainingLength = remainingLength(msg, remainingLengthWithoutProperties, propertyLength);
encodedLength = encodedPacketLength(remainingLength);
}
msg.setEncodedLength(encodedLength);
msg.setOmittedProperties(omittedProperties);
msg.setRemainingLength(remainingLength);
msg.setPropertyLength(propertyLength);
return encodedLength;
}
private static long calculateMaxMessageSize(final @NotNull ClientConnectionContext clientConnectionContext) {
Preconditions.checkNotNull(clientConnectionContext, "ClientContext must never be null");
final Long maxMessageSize = clientConnectionContext.getMaxPacketSizeSend();
return Objects.requireNonNullElse(maxMessageSize, (long) MAXIMUM_PACKET_SIZE_LIMIT);
}
abstract void encode(@NotNull T message, @NotNull ByteBuf out);
public int remainingLength(
final @NotNull T message, final int remainingLengthWithoutProperties, final int propertyLength) {
return remainingLengthWithoutProperties + encodedPropertyLengthWithHeader(message, propertyLength);
}
/**
* Calculates the remaining length byte count without the properties of the MQTT message.
*
* @return the remaining length without the properties of the MQTT message.
*/
abstract int calculateRemainingLengthWithoutProperties(@NotNull T message);
/**
* Calculates the property length byte count of the MQTT message.
*
* @return the property length of the MQTT message.
*/
abstract int calculatePropertyLength(@NotNull T message);
int propertyLength(final @NotNull T message, final int propertyLength, final int omittedProperties) {
switch (omittedProperties) {
case 0:
return propertyLength;
case 1:
return propertyLength - getUserProperties(message).encodedLength();
default:
return -1;
}
}
/**
* Calculates the encoded property length with a prefixed header.
*
* @param propertyLength the encoded property length.
* @return the encoded property length with a prefixed header.
*/
int encodedPropertyLengthWithHeader(final @NotNull T message, final int propertyLength) {
return encodedLengthWithHeader(propertyLength);
}
/**
* @return the length of the omissible properties of the MQTT message.
*/
int omissiblePropertiesLength(final @NotNull T message) {
return getUserProperties(message).encodedLength();
}
/**
* Encodes the omissible properties of the MQTT message if they must not be omitted due to the given maximum packet
* size.
*
* @param out the byte buffer to encode to.
*/
void encodeOmissibleProperties(final @NotNull T message, final @NotNull ByteBuf out) {
if (message.getOmittedProperties() == 0) {
getUserProperties(message).encode(out);
}
}
abstract @NotNull Mqtt5UserProperties getUserProperties(@NotNull T message);
/**
* Base class for encoders of MQTT messages with an omissible Reason String and omissible User Properties.
*/
abstract static class Mqtt5MessageWithReasonStringEncoder
extends Mqtt5MessageWithUserPropertiesEncoder {
Mqtt5MessageWithReasonStringEncoder(
final @NotNull MessageDroppedService messageDroppedService,
final @NotNull SecurityConfigurationService securityConfigurationService) {
super(messageDroppedService, securityConfigurationService);
}
@Override
int propertyLength(final @NotNull M message, final int propertyLength, final int omittedProperties) {
switch (omittedProperties) {
case 0:
return propertyLength;
case 1:
return propertyLength - reasonStringLength(message);
case 2:
return propertyLength - getUserProperties(message).encodedLength();
default:
return -1;
}
}
final int omissiblePropertiesLength(final @NotNull M message) {
return reasonStringLength(message) + getUserProperties(message).encodedLength();
}
@Override
void encodeOmissibleProperties(final @NotNull M message, final @NotNull ByteBuf out) {
if (message.getOmittedProperties() == 0) {
encodeNullableProperty(REASON_STRING, message.getReasonString(), out);
}
if (message.getOmittedProperties() <= 1) {
getUserProperties(message).encode(out);
}
}
private int reasonStringLength(final @NotNull M message) {
return nullablePropertyEncodedLength(message.getReasonString());
}
@Override
@NotNull Mqtt5UserProperties getUserProperties(final @NotNull M message) {
return message.getUserProperties();
}
}
/**
* Base class for encoders of MQTT messages with an omissible Reason Code, an omissible Reason String and omissible
* User Properties. The Reason Code is omitted if it is the default and the property length is 0.
*/
abstract static class Mqtt5MessageWithOmissibleReasonCodeEncoder, R extends Mqtt5ReasonCode>
extends Mqtt5MessageWithReasonStringEncoder {
Mqtt5MessageWithOmissibleReasonCodeEncoder(
final @NotNull MessageDroppedService messageDroppedService,
final @NotNull SecurityConfigurationService securityConfigurationService) {
super(messageDroppedService, securityConfigurationService);
}
abstract int getFixedHeader();
abstract @NotNull R getDefaultReasonCode();
@Override
final int calculateRemainingLengthWithoutProperties(final @NotNull M message) {
return 1 + additionalRemainingLength(message); // reason code (1)
}
int additionalRemainingLength(final @NotNull M message) {
return 0;
}
@Override
final int calculatePropertyLength(final @NotNull M message) {
return omissiblePropertiesLength(message) + additionalPropertyLength(message);
}
int additionalPropertyLength(final @NotNull M message) {
return 0;
}
@Override
void encode(final @NotNull M message, final @NotNull ByteBuf out) {
encodeFixedHeader(out, message.getRemainingLength());
encodeVariableHeader(message, out);
}
private void encodeFixedHeader(final @NotNull ByteBuf out, final int remainingLength) {
out.writeByte(getFixedHeader());
MqttVariableByteInteger.encode(remainingLength, out);
}
private void encodeVariableHeader(final @NotNull M message, final @NotNull ByteBuf out) {
encodeAdditionalVariableHeader(message, out);
final R reasonCode = message.getReasonCode();
if (message.getPropertyLength() == 0) {
if (reasonCode != getDefaultReasonCode()) {
out.writeByte(reasonCode.getCode());
}
} else {
out.writeByte(reasonCode.getCode());
MqttVariableByteInteger.encode(message.getPropertyLength(), out);
encodeAdditionalProperties(message, out);
encodeOmissibleProperties(message, out);
}
}
void encodeAdditionalVariableHeader(final @NotNull M message, final @NotNull ByteBuf out) {
}
void encodeAdditionalProperties(final @NotNull M message, final @NotNull ByteBuf out) {
}
@Override
final int encodedPropertyLengthWithHeader(final @NotNull M message, final int propertyLength) {
if (propertyLength == 0) {
return (message.getReasonCode() == getDefaultReasonCode()) ? -1 : 0;
}
return super.encodedPropertyLengthWithHeader(message, propertyLength);
}
}
/**
* Base class for encoders of MQTT messages with an Packet Identifier, an omissible Reason Code, an omissible Reason
* String and omissible User Properties. The Reason Code is omitted if it is the default and the property length is
* 0.
*/
abstract static class Mqtt5MessageWithIdAndOmissibleReasonCodeEncoder, R extends Mqtt5ReasonCode>
extends Mqtt5MessageWithOmissibleReasonCodeEncoder {
Mqtt5MessageWithIdAndOmissibleReasonCodeEncoder(
final @NotNull MessageDroppedService messageDroppedService,
final @NotNull SecurityConfigurationService securityConfigurationService) {
super(messageDroppedService, securityConfigurationService);
}
@Override
int additionalRemainingLength(final @NotNull M message) {
return 2; // packet identifier (2)
}
@Override
void encodeAdditionalVariableHeader(final @NotNull M message, final @NotNull ByteBuf out) {
out.writeShort(message.getPacketIdentifier());
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy