2014-04-16 23:59:09 +02:00
|
|
|
/*
|
|
|
|
* Copyright 2014 The Netty Project
|
|
|
|
*
|
|
|
|
* The Netty Project licenses this file to you 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 io.netty.handler.codec.mqtt;
|
|
|
|
|
|
|
|
import io.netty.buffer.ByteBuf;
|
|
|
|
import io.netty.channel.ChannelHandlerContext;
|
|
|
|
import io.netty.handler.codec.DecoderException;
|
|
|
|
import io.netty.handler.codec.ReplayingDecoder;
|
|
|
|
import io.netty.handler.codec.mqtt.MqttDecoder.DecoderState;
|
|
|
|
import io.netty.util.CharsetUtil;
|
|
|
|
|
|
|
|
import java.util.ArrayList;
|
|
|
|
import java.util.List;
|
|
|
|
|
2016-04-14 10:31:48 +02:00
|
|
|
import static io.netty.handler.codec.mqtt.MqttCodecUtil.isValidClientId;
|
|
|
|
import static io.netty.handler.codec.mqtt.MqttCodecUtil.isValidMessageId;
|
|
|
|
import static io.netty.handler.codec.mqtt.MqttCodecUtil.isValidPublishTopicName;
|
|
|
|
import static io.netty.handler.codec.mqtt.MqttCodecUtil.resetUnusedFields;
|
|
|
|
import static io.netty.handler.codec.mqtt.MqttCodecUtil.validateFixedHeader;
|
2014-04-16 23:59:09 +02:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Decodes Mqtt messages from bytes, following
|
|
|
|
* <a href="http://public.dhe.ibm.com/software/dw/webservices/ws-mqtt/mqtt-v3r1.html">
|
2017-04-19 22:37:03 +02:00
|
|
|
* the MQTT protocol specification v3.1</a>
|
2014-04-16 23:59:09 +02:00
|
|
|
*/
|
2016-03-22 14:29:34 +01:00
|
|
|
public final class MqttDecoder extends ReplayingDecoder<DecoderState> {
|
2014-04-16 23:59:09 +02:00
|
|
|
|
|
|
|
private static final int DEFAULT_MAX_BYTES_IN_MESSAGE = 8092;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* States of the decoder.
|
|
|
|
* We start at READ_FIXED_HEADER, followed by
|
|
|
|
* READ_VARIABLE_HEADER and finally READ_PAYLOAD.
|
|
|
|
*/
|
|
|
|
enum DecoderState {
|
|
|
|
READ_FIXED_HEADER,
|
|
|
|
READ_VARIABLE_HEADER,
|
|
|
|
READ_PAYLOAD,
|
|
|
|
BAD_MESSAGE,
|
|
|
|
}
|
|
|
|
|
|
|
|
private MqttFixedHeader mqttFixedHeader;
|
|
|
|
private Object variableHeader;
|
|
|
|
private int bytesRemainingInVariablePart;
|
|
|
|
|
|
|
|
private final int maxBytesInMessage;
|
|
|
|
|
|
|
|
public MqttDecoder() {
|
|
|
|
this(DEFAULT_MAX_BYTES_IN_MESSAGE);
|
|
|
|
}
|
|
|
|
|
|
|
|
public MqttDecoder(int maxBytesInMessage) {
|
|
|
|
super(DecoderState.READ_FIXED_HEADER);
|
|
|
|
this.maxBytesInMessage = maxBytesInMessage;
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
2019-12-16 21:00:32 +01:00
|
|
|
protected void decode(ChannelHandlerContext ctx, ByteBuf buffer) throws Exception {
|
2014-04-16 23:59:09 +02:00
|
|
|
switch (state()) {
|
2017-07-18 12:26:54 +02:00
|
|
|
case READ_FIXED_HEADER: try {
|
2014-04-16 23:59:09 +02:00
|
|
|
mqttFixedHeader = decodeFixedHeader(buffer);
|
|
|
|
bytesRemainingInVariablePart = mqttFixedHeader.remainingLength();
|
|
|
|
checkpoint(DecoderState.READ_VARIABLE_HEADER);
|
|
|
|
// fall through
|
2017-07-18 12:26:54 +02:00
|
|
|
} catch (Exception cause) {
|
2019-12-16 21:00:32 +01:00
|
|
|
ctx.fireChannelRead(invalidMessage(cause));
|
2017-07-18 12:26:54 +02:00
|
|
|
return;
|
|
|
|
}
|
2014-04-16 23:59:09 +02:00
|
|
|
|
|
|
|
case READ_VARIABLE_HEADER: try {
|
2018-08-31 15:06:09 +02:00
|
|
|
final Result<?> decodedVariableHeader = decodeVariableHeader(buffer, mqttFixedHeader);
|
|
|
|
variableHeader = decodedVariableHeader.value;
|
2014-04-16 23:59:09 +02:00
|
|
|
if (bytesRemainingInVariablePart > maxBytesInMessage) {
|
2014-06-21 09:46:09 +02:00
|
|
|
throw new DecoderException("too large message: " + bytesRemainingInVariablePart + " bytes");
|
2014-04-16 23:59:09 +02:00
|
|
|
}
|
|
|
|
bytesRemainingInVariablePart -= decodedVariableHeader.numberOfBytesConsumed;
|
|
|
|
checkpoint(DecoderState.READ_PAYLOAD);
|
|
|
|
// fall through
|
|
|
|
} catch (Exception cause) {
|
2019-12-16 21:00:32 +01:00
|
|
|
ctx.fireChannelRead(invalidMessage(cause));
|
2014-04-16 23:59:09 +02:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
case READ_PAYLOAD: try {
|
|
|
|
final Result<?> decodedPayload =
|
|
|
|
decodePayload(
|
|
|
|
buffer,
|
|
|
|
mqttFixedHeader.messageType(),
|
|
|
|
bytesRemainingInVariablePart,
|
|
|
|
variableHeader);
|
|
|
|
bytesRemainingInVariablePart -= decodedPayload.numberOfBytesConsumed;
|
|
|
|
if (bytesRemainingInVariablePart != 0) {
|
|
|
|
throw new DecoderException(
|
2014-06-21 09:46:09 +02:00
|
|
|
"non-zero remaining payload bytes: " +
|
|
|
|
bytesRemainingInVariablePart + " (" + mqttFixedHeader.messageType() + ')');
|
2014-04-16 23:59:09 +02:00
|
|
|
}
|
|
|
|
checkpoint(DecoderState.READ_FIXED_HEADER);
|
2017-03-08 18:03:23 +01:00
|
|
|
MqttMessage message = MqttMessageFactory.newMessage(
|
|
|
|
mqttFixedHeader, variableHeader, decodedPayload.value);
|
2014-04-16 23:59:09 +02:00
|
|
|
mqttFixedHeader = null;
|
|
|
|
variableHeader = null;
|
2019-12-16 21:00:32 +01:00
|
|
|
ctx.fireChannelRead(message);
|
2014-04-16 23:59:09 +02:00
|
|
|
break;
|
|
|
|
} catch (Exception cause) {
|
2019-12-16 21:00:32 +01:00
|
|
|
ctx.fireChannelRead(invalidMessage(cause));
|
2014-04-16 23:59:09 +02:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
case BAD_MESSAGE:
|
|
|
|
// Keep discarding until disconnection.
|
|
|
|
buffer.skipBytes(actualReadableBytes());
|
|
|
|
break;
|
|
|
|
|
|
|
|
default:
|
2014-06-21 09:46:09 +02:00
|
|
|
// Shouldn't reach here.
|
|
|
|
throw new Error();
|
2014-04-16 23:59:09 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
private MqttMessage invalidMessage(Throwable cause) {
|
|
|
|
checkpoint(DecoderState.BAD_MESSAGE);
|
2018-08-31 15:06:09 +02:00
|
|
|
return MqttMessageFactory.newInvalidMessage(mqttFixedHeader, variableHeader, cause);
|
2014-04-16 23:59:09 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Decodes the fixed header. It's one byte for the flags and then variable bytes for the remaining length.
|
|
|
|
*
|
|
|
|
* @param buffer the buffer to decode from
|
|
|
|
* @return the fixed header
|
|
|
|
*/
|
|
|
|
private static MqttFixedHeader decodeFixedHeader(ByteBuf buffer) {
|
|
|
|
short b1 = buffer.readUnsignedByte();
|
|
|
|
|
|
|
|
MqttMessageType messageType = MqttMessageType.valueOf(b1 >> 4);
|
|
|
|
boolean dupFlag = (b1 & 0x08) == 0x08;
|
|
|
|
int qosLevel = (b1 & 0x06) >> 1;
|
|
|
|
boolean retain = (b1 & 0x01) != 0;
|
|
|
|
|
|
|
|
int remainingLength = 0;
|
|
|
|
int multiplier = 1;
|
|
|
|
short digit;
|
|
|
|
int loops = 0;
|
|
|
|
do {
|
|
|
|
digit = buffer.readUnsignedByte();
|
|
|
|
remainingLength += (digit & 127) * multiplier;
|
|
|
|
multiplier *= 128;
|
|
|
|
loops++;
|
|
|
|
} while ((digit & 128) != 0 && loops < 4);
|
|
|
|
|
|
|
|
// MQTT protocol limits Remaining Length to 4 bytes
|
|
|
|
if (loops == 4 && (digit & 128) != 0) {
|
2014-06-21 09:46:09 +02:00
|
|
|
throw new DecoderException("remaining length exceeds 4 digits (" + messageType + ')');
|
2014-04-16 23:59:09 +02:00
|
|
|
}
|
|
|
|
MqttFixedHeader decodedFixedHeader =
|
2014-06-21 09:46:09 +02:00
|
|
|
new MqttFixedHeader(messageType, dupFlag, MqttQoS.valueOf(qosLevel), retain, remainingLength);
|
2014-04-16 23:59:09 +02:00
|
|
|
return validateFixedHeader(resetUnusedFields(decodedFixedHeader));
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Decodes the variable header (if any)
|
|
|
|
* @param buffer the buffer to decode from
|
|
|
|
* @param mqttFixedHeader MqttFixedHeader of the same message
|
|
|
|
* @return the variable header
|
|
|
|
*/
|
|
|
|
private static Result<?> decodeVariableHeader(ByteBuf buffer, MqttFixedHeader mqttFixedHeader) {
|
|
|
|
switch (mqttFixedHeader.messageType()) {
|
|
|
|
case CONNECT:
|
|
|
|
return decodeConnectionVariableHeader(buffer);
|
|
|
|
|
|
|
|
case CONNACK:
|
|
|
|
return decodeConnAckVariableHeader(buffer);
|
|
|
|
|
|
|
|
case SUBSCRIBE:
|
|
|
|
case UNSUBSCRIBE:
|
|
|
|
case SUBACK:
|
|
|
|
case UNSUBACK:
|
|
|
|
case PUBACK:
|
|
|
|
case PUBREC:
|
|
|
|
case PUBCOMP:
|
|
|
|
case PUBREL:
|
|
|
|
return decodeMessageIdVariableHeader(buffer);
|
|
|
|
|
|
|
|
case PUBLISH:
|
|
|
|
return decodePublishVariableHeader(buffer, mqttFixedHeader);
|
|
|
|
|
|
|
|
case PINGREQ:
|
|
|
|
case PINGRESP:
|
|
|
|
case DISCONNECT:
|
|
|
|
// Empty variable header
|
2019-01-22 16:07:26 +01:00
|
|
|
return new Result<>(null, 0);
|
2014-04-16 23:59:09 +02:00
|
|
|
}
|
2019-01-22 16:07:26 +01:00
|
|
|
return new Result<>(null, 0); //should never reach here
|
2014-04-16 23:59:09 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
private static Result<MqttConnectVariableHeader> decodeConnectionVariableHeader(ByteBuf buffer) {
|
|
|
|
final Result<String> protoString = decodeString(buffer);
|
|
|
|
int numberOfBytesConsumed = protoString.numberOfBytesConsumed;
|
|
|
|
|
2014-11-14 11:59:45 +01:00
|
|
|
final byte protocolLevel = buffer.readByte();
|
|
|
|
numberOfBytesConsumed += 1;
|
|
|
|
|
|
|
|
final MqttVersion mqttVersion = MqttVersion.fromProtocolNameAndLevel(protoString.value, protocolLevel);
|
|
|
|
|
2014-04-16 23:59:09 +02:00
|
|
|
final int b1 = buffer.readUnsignedByte();
|
2014-11-14 11:59:45 +01:00
|
|
|
numberOfBytesConsumed += 1;
|
2014-04-16 23:59:09 +02:00
|
|
|
|
|
|
|
final Result<Integer> keepAlive = decodeMsbLsb(buffer);
|
|
|
|
numberOfBytesConsumed += keepAlive.numberOfBytesConsumed;
|
|
|
|
|
|
|
|
final boolean hasUserName = (b1 & 0x80) == 0x80;
|
|
|
|
final boolean hasPassword = (b1 & 0x40) == 0x40;
|
|
|
|
final boolean willRetain = (b1 & 0x20) == 0x20;
|
|
|
|
final int willQos = (b1 & 0x18) >> 3;
|
|
|
|
final boolean willFlag = (b1 & 0x04) == 0x04;
|
|
|
|
final boolean cleanSession = (b1 & 0x02) == 0x02;
|
2016-03-16 06:25:39 +01:00
|
|
|
if (mqttVersion == MqttVersion.MQTT_3_1_1) {
|
|
|
|
final boolean zeroReservedFlag = (b1 & 0x01) == 0x0;
|
|
|
|
if (!zeroReservedFlag) {
|
|
|
|
// MQTT v3.1.1: The Server MUST validate that the reserved flag in the CONNECT Control Packet is
|
|
|
|
// set to zero and disconnect the Client if it is not zero.
|
|
|
|
// See http://docs.oasis-open.org/mqtt/mqtt/v3.1.1/os/mqtt-v3.1.1-os.html#_Toc385349230
|
|
|
|
throw new DecoderException("non-zero reserved flag");
|
|
|
|
}
|
|
|
|
}
|
2014-04-16 23:59:09 +02:00
|
|
|
|
|
|
|
final MqttConnectVariableHeader mqttConnectVariableHeader = new MqttConnectVariableHeader(
|
2014-11-14 11:59:45 +01:00
|
|
|
mqttVersion.protocolName(),
|
|
|
|
mqttVersion.protocolLevel(),
|
2014-04-16 23:59:09 +02:00
|
|
|
hasUserName,
|
|
|
|
hasPassword,
|
|
|
|
willRetain,
|
|
|
|
willQos,
|
|
|
|
willFlag,
|
|
|
|
cleanSession,
|
|
|
|
keepAlive.value);
|
2019-01-22 16:07:26 +01:00
|
|
|
return new Result<>(mqttConnectVariableHeader, numberOfBytesConsumed);
|
2014-04-16 23:59:09 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
private static Result<MqttConnAckVariableHeader> decodeConnAckVariableHeader(ByteBuf buffer) {
|
2015-07-29 14:00:18 +02:00
|
|
|
final boolean sessionPresent = (buffer.readUnsignedByte() & 0x01) == 0x01;
|
2014-04-16 23:59:09 +02:00
|
|
|
byte returnCode = buffer.readByte();
|
|
|
|
final int numberOfBytesConsumed = 2;
|
|
|
|
final MqttConnAckVariableHeader mqttConnAckVariableHeader =
|
2015-07-29 14:00:18 +02:00
|
|
|
new MqttConnAckVariableHeader(MqttConnectReturnCode.valueOf(returnCode), sessionPresent);
|
2019-01-22 16:07:26 +01:00
|
|
|
return new Result<>(mqttConnAckVariableHeader, numberOfBytesConsumed);
|
2014-04-16 23:59:09 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
private static Result<MqttMessageIdVariableHeader> decodeMessageIdVariableHeader(ByteBuf buffer) {
|
|
|
|
final Result<Integer> messageId = decodeMessageId(buffer);
|
2019-01-22 16:07:26 +01:00
|
|
|
return new Result<>(
|
2014-04-16 23:59:09 +02:00
|
|
|
MqttMessageIdVariableHeader.from(messageId.value),
|
|
|
|
messageId.numberOfBytesConsumed);
|
|
|
|
}
|
|
|
|
|
|
|
|
private static Result<MqttPublishVariableHeader> decodePublishVariableHeader(
|
|
|
|
ByteBuf buffer,
|
|
|
|
MqttFixedHeader mqttFixedHeader) {
|
|
|
|
final Result<String> decodedTopic = decodeString(buffer);
|
|
|
|
if (!isValidPublishTopicName(decodedTopic.value)) {
|
2014-06-21 09:46:09 +02:00
|
|
|
throw new DecoderException("invalid publish topic name: " + decodedTopic.value + " (contains wildcards)");
|
2014-04-16 23:59:09 +02:00
|
|
|
}
|
|
|
|
int numberOfBytesConsumed = decodedTopic.numberOfBytesConsumed;
|
|
|
|
|
|
|
|
int messageId = -1;
|
|
|
|
if (mqttFixedHeader.qosLevel().value() > 0) {
|
|
|
|
final Result<Integer> decodedMessageId = decodeMessageId(buffer);
|
|
|
|
messageId = decodedMessageId.value;
|
|
|
|
numberOfBytesConsumed += decodedMessageId.numberOfBytesConsumed;
|
|
|
|
}
|
|
|
|
final MqttPublishVariableHeader mqttPublishVariableHeader =
|
|
|
|
new MqttPublishVariableHeader(decodedTopic.value, messageId);
|
2019-01-22 16:07:26 +01:00
|
|
|
return new Result<>(mqttPublishVariableHeader, numberOfBytesConsumed);
|
2014-04-16 23:59:09 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
private static Result<Integer> decodeMessageId(ByteBuf buffer) {
|
|
|
|
final Result<Integer> messageId = decodeMsbLsb(buffer);
|
|
|
|
if (!isValidMessageId(messageId.value)) {
|
2014-06-21 09:46:09 +02:00
|
|
|
throw new DecoderException("invalid messageId: " + messageId.value);
|
2014-04-16 23:59:09 +02:00
|
|
|
}
|
|
|
|
return messageId;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Decodes the payload.
|
|
|
|
*
|
|
|
|
* @param buffer the buffer to decode from
|
|
|
|
* @param messageType type of the message being decoded
|
|
|
|
* @param bytesRemainingInVariablePart bytes remaining
|
|
|
|
* @param variableHeader variable header of the same message
|
|
|
|
* @return the payload
|
|
|
|
*/
|
|
|
|
private static Result<?> decodePayload(
|
|
|
|
ByteBuf buffer,
|
|
|
|
MqttMessageType messageType,
|
|
|
|
int bytesRemainingInVariablePart,
|
|
|
|
Object variableHeader) {
|
|
|
|
switch (messageType) {
|
|
|
|
case CONNECT:
|
|
|
|
return decodeConnectionPayload(buffer, (MqttConnectVariableHeader) variableHeader);
|
|
|
|
|
|
|
|
case SUBSCRIBE:
|
|
|
|
return decodeSubscribePayload(buffer, bytesRemainingInVariablePart);
|
|
|
|
|
|
|
|
case SUBACK:
|
|
|
|
return decodeSubackPayload(buffer, bytesRemainingInVariablePart);
|
|
|
|
|
|
|
|
case UNSUBSCRIBE:
|
|
|
|
return decodeUnsubscribePayload(buffer, bytesRemainingInVariablePart);
|
|
|
|
|
|
|
|
case PUBLISH:
|
|
|
|
return decodePublishPayload(buffer, bytesRemainingInVariablePart);
|
|
|
|
|
|
|
|
default:
|
|
|
|
// unknown payload , no byte consumed
|
2019-01-22 16:07:26 +01:00
|
|
|
return new Result<>(null, 0);
|
2014-04-16 23:59:09 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
private static Result<MqttConnectPayload> decodeConnectionPayload(
|
|
|
|
ByteBuf buffer,
|
|
|
|
MqttConnectVariableHeader mqttConnectVariableHeader) {
|
|
|
|
final Result<String> decodedClientId = decodeString(buffer);
|
|
|
|
final String decodedClientIdValue = decodedClientId.value;
|
2014-11-14 11:59:45 +01:00
|
|
|
final MqttVersion mqttVersion = MqttVersion.fromProtocolNameAndLevel(mqttConnectVariableHeader.name(),
|
|
|
|
(byte) mqttConnectVariableHeader.version());
|
|
|
|
if (!isValidClientId(mqttVersion, decodedClientIdValue)) {
|
2014-10-08 18:38:20 +02:00
|
|
|
throw new MqttIdentifierRejectedException("invalid clientIdentifier: " + decodedClientIdValue);
|
2014-04-16 23:59:09 +02:00
|
|
|
}
|
|
|
|
int numberOfBytesConsumed = decodedClientId.numberOfBytesConsumed;
|
|
|
|
|
|
|
|
Result<String> decodedWillTopic = null;
|
2017-06-06 02:32:44 +02:00
|
|
|
Result<byte[]> decodedWillMessage = null;
|
2014-04-16 23:59:09 +02:00
|
|
|
if (mqttConnectVariableHeader.isWillFlag()) {
|
|
|
|
decodedWillTopic = decodeString(buffer, 0, 32767);
|
|
|
|
numberOfBytesConsumed += decodedWillTopic.numberOfBytesConsumed;
|
2017-06-06 02:32:44 +02:00
|
|
|
decodedWillMessage = decodeByteArray(buffer);
|
2014-04-16 23:59:09 +02:00
|
|
|
numberOfBytesConsumed += decodedWillMessage.numberOfBytesConsumed;
|
|
|
|
}
|
|
|
|
Result<String> decodedUserName = null;
|
2017-06-06 02:32:44 +02:00
|
|
|
Result<byte[]> decodedPassword = null;
|
2014-04-16 23:59:09 +02:00
|
|
|
if (mqttConnectVariableHeader.hasUserName()) {
|
|
|
|
decodedUserName = decodeString(buffer);
|
|
|
|
numberOfBytesConsumed += decodedUserName.numberOfBytesConsumed;
|
|
|
|
}
|
|
|
|
if (mqttConnectVariableHeader.hasPassword()) {
|
2017-06-06 02:32:44 +02:00
|
|
|
decodedPassword = decodeByteArray(buffer);
|
2014-04-16 23:59:09 +02:00
|
|
|
numberOfBytesConsumed += decodedPassword.numberOfBytesConsumed;
|
|
|
|
}
|
|
|
|
|
|
|
|
final MqttConnectPayload mqttConnectPayload =
|
|
|
|
new MqttConnectPayload(
|
|
|
|
decodedClientId.value,
|
2014-07-19 19:51:19 +02:00
|
|
|
decodedWillTopic != null ? decodedWillTopic.value : null,
|
|
|
|
decodedWillMessage != null ? decodedWillMessage.value : null,
|
|
|
|
decodedUserName != null ? decodedUserName.value : null,
|
|
|
|
decodedPassword != null ? decodedPassword.value : null);
|
2019-01-22 16:07:26 +01:00
|
|
|
return new Result<>(mqttConnectPayload, numberOfBytesConsumed);
|
2014-04-16 23:59:09 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
private static Result<MqttSubscribePayload> decodeSubscribePayload(
|
|
|
|
ByteBuf buffer,
|
|
|
|
int bytesRemainingInVariablePart) {
|
2019-01-22 16:07:26 +01:00
|
|
|
final List<MqttTopicSubscription> subscribeTopics = new ArrayList<>();
|
2014-04-16 23:59:09 +02:00
|
|
|
int numberOfBytesConsumed = 0;
|
|
|
|
while (numberOfBytesConsumed < bytesRemainingInVariablePart) {
|
|
|
|
final Result<String> decodedTopicName = decodeString(buffer);
|
|
|
|
numberOfBytesConsumed += decodedTopicName.numberOfBytesConsumed;
|
|
|
|
int qos = buffer.readUnsignedByte() & 0x03;
|
|
|
|
numberOfBytesConsumed++;
|
2014-06-21 09:46:09 +02:00
|
|
|
subscribeTopics.add(new MqttTopicSubscription(decodedTopicName.value, MqttQoS.valueOf(qos)));
|
2014-04-16 23:59:09 +02:00
|
|
|
}
|
2019-01-22 16:07:26 +01:00
|
|
|
return new Result<>(new MqttSubscribePayload(subscribeTopics), numberOfBytesConsumed);
|
2014-04-16 23:59:09 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
private static Result<MqttSubAckPayload> decodeSubackPayload(
|
|
|
|
ByteBuf buffer,
|
|
|
|
int bytesRemainingInVariablePart) {
|
2019-01-22 16:07:26 +01:00
|
|
|
final List<Integer> grantedQos = new ArrayList<>();
|
2014-04-16 23:59:09 +02:00
|
|
|
int numberOfBytesConsumed = 0;
|
|
|
|
while (numberOfBytesConsumed < bytesRemainingInVariablePart) {
|
2018-02-04 01:16:11 +01:00
|
|
|
int qos = buffer.readUnsignedByte();
|
|
|
|
if (qos != MqttQoS.FAILURE.value()) {
|
|
|
|
qos &= 0x03;
|
|
|
|
}
|
2014-04-16 23:59:09 +02:00
|
|
|
numberOfBytesConsumed++;
|
|
|
|
grantedQos.add(qos);
|
|
|
|
}
|
2019-01-22 16:07:26 +01:00
|
|
|
return new Result<>(new MqttSubAckPayload(grantedQos), numberOfBytesConsumed);
|
2014-04-16 23:59:09 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
private static Result<MqttUnsubscribePayload> decodeUnsubscribePayload(
|
|
|
|
ByteBuf buffer,
|
|
|
|
int bytesRemainingInVariablePart) {
|
2019-01-22 16:07:26 +01:00
|
|
|
final List<String> unsubscribeTopics = new ArrayList<>();
|
2014-04-16 23:59:09 +02:00
|
|
|
int numberOfBytesConsumed = 0;
|
|
|
|
while (numberOfBytesConsumed < bytesRemainingInVariablePart) {
|
|
|
|
final Result<String> decodedTopicName = decodeString(buffer);
|
|
|
|
numberOfBytesConsumed += decodedTopicName.numberOfBytesConsumed;
|
|
|
|
unsubscribeTopics.add(decodedTopicName.value);
|
|
|
|
}
|
2019-01-22 16:07:26 +01:00
|
|
|
return new Result<>(
|
2014-04-16 23:59:09 +02:00
|
|
|
new MqttUnsubscribePayload(unsubscribeTopics),
|
|
|
|
numberOfBytesConsumed);
|
|
|
|
}
|
|
|
|
|
|
|
|
private static Result<ByteBuf> decodePublishPayload(ByteBuf buffer, int bytesRemainingInVariablePart) {
|
2016-04-14 10:31:48 +02:00
|
|
|
ByteBuf b = buffer.readRetainedSlice(bytesRemainingInVariablePart);
|
2019-01-22 16:07:26 +01:00
|
|
|
return new Result<>(b, bytesRemainingInVariablePart);
|
2014-04-16 23:59:09 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
private static Result<String> decodeString(ByteBuf buffer) {
|
|
|
|
return decodeString(buffer, 0, Integer.MAX_VALUE);
|
|
|
|
}
|
|
|
|
|
|
|
|
private static Result<String> decodeString(ByteBuf buffer, int minBytes, int maxBytes) {
|
|
|
|
final Result<Integer> decodedSize = decodeMsbLsb(buffer);
|
|
|
|
int size = decodedSize.value;
|
|
|
|
int numberOfBytesConsumed = decodedSize.numberOfBytesConsumed;
|
|
|
|
if (size < minBytes || size > maxBytes) {
|
|
|
|
buffer.skipBytes(size);
|
|
|
|
numberOfBytesConsumed += size;
|
2019-01-22 16:07:26 +01:00
|
|
|
return new Result<>(null, numberOfBytesConsumed);
|
2014-04-16 23:59:09 +02:00
|
|
|
}
|
2016-04-07 10:16:26 +02:00
|
|
|
String s = buffer.toString(buffer.readerIndex(), size, CharsetUtil.UTF_8);
|
|
|
|
buffer.skipBytes(size);
|
2014-04-16 23:59:09 +02:00
|
|
|
numberOfBytesConsumed += size;
|
2019-01-22 16:07:26 +01:00
|
|
|
return new Result<>(s, numberOfBytesConsumed);
|
2014-04-16 23:59:09 +02:00
|
|
|
}
|
|
|
|
|
2017-06-06 02:32:44 +02:00
|
|
|
private static Result<byte[]> decodeByteArray(ByteBuf buffer) {
|
|
|
|
final Result<Integer> decodedSize = decodeMsbLsb(buffer);
|
|
|
|
int size = decodedSize.value;
|
|
|
|
byte[] bytes = new byte[size];
|
|
|
|
buffer.readBytes(bytes);
|
2019-01-22 16:07:26 +01:00
|
|
|
return new Result<>(bytes, decodedSize.numberOfBytesConsumed + size);
|
2017-06-06 02:32:44 +02:00
|
|
|
}
|
|
|
|
|
2014-04-16 23:59:09 +02:00
|
|
|
private static Result<Integer> decodeMsbLsb(ByteBuf buffer) {
|
|
|
|
return decodeMsbLsb(buffer, 0, 65535);
|
|
|
|
}
|
|
|
|
|
|
|
|
private static Result<Integer> decodeMsbLsb(ByteBuf buffer, int min, int max) {
|
|
|
|
short msbSize = buffer.readUnsignedByte();
|
|
|
|
short lsbSize = buffer.readUnsignedByte();
|
|
|
|
final int numberOfBytesConsumed = 2;
|
|
|
|
int result = msbSize << 8 | lsbSize;
|
|
|
|
if (result < min || result > max) {
|
|
|
|
result = -1;
|
|
|
|
}
|
2019-01-22 16:07:26 +01:00
|
|
|
return new Result<>(result, numberOfBytesConsumed);
|
2014-04-16 23:59:09 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
private static final class Result<T> {
|
|
|
|
|
|
|
|
private final T value;
|
|
|
|
private final int numberOfBytesConsumed;
|
|
|
|
|
|
|
|
Result(T value, int numberOfBytesConsumed) {
|
|
|
|
this.value = value;
|
|
|
|
this.numberOfBytesConsumed = numberOfBytesConsumed;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|