View Javadoc
1   /*
2    * Copyright 2014 The Netty Project
3    *
4    * The Netty Project licenses this file to you under the Apache License,
5    * version 2.0 (the "License"); you may not use this file except in compliance
6    * with the License. You may obtain a copy of the License at:
7    *
8    *   https://www.apache.org/licenses/LICENSE-2.0
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
12   * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13   * License for the specific language governing permissions and limitations
14   * under the License.
15   */
16  
17  package io.netty.handler.codec.mqtt;
18  
19  import io.netty.channel.ChannelHandlerContext;
20  import io.netty.handler.codec.DecoderException;
21  import io.netty.util.Attribute;
22  import io.netty.util.AttributeKey;
23  
24  import static io.netty.handler.codec.mqtt.MqttConstant.MIN_CLIENT_ID_LENGTH;
25  
26  final class MqttCodecUtil {
27  
28      static final AttributeKey<MqttVersion> MQTT_VERSION_KEY = AttributeKey.valueOf("NETTY_CODEC_MQTT_VERSION");
29  
30      static MqttVersion getMqttVersion(ChannelHandlerContext ctx) {
31          Attribute<MqttVersion> attr = ctx.channel().attr(MQTT_VERSION_KEY);
32          MqttVersion version = attr.get();
33          if (version == null) {
34              return MqttVersion.MQTT_3_1_1;
35          }
36          return version;
37      }
38  
39      static void setMqttVersion(ChannelHandlerContext ctx, MqttVersion version) {
40          Attribute<MqttVersion> attr = ctx.channel().attr(MQTT_VERSION_KEY);
41          attr.set(version);
42      }
43  
44      static boolean isValidPublishTopicName(String topicName) {
45          if (topicName == null) {
46              return false;
47          }
48          // publish topic name must not contain any wildcard
49          for (int i = 0; i < topicName.length(); i++) {
50              char c = topicName.charAt(i);
51              if (c == '#' || c == '+' || c == '\0') {
52                  return false;
53              }
54          }
55          return true;
56      }
57  
58      static boolean isValidMessageId(int messageId) {
59          return messageId != 0;
60      }
61  
62      static boolean isValidUserName(String userName) {
63          return userName == null || userName.indexOf('\0') == -1;
64      }
65  
66      /**
67       * Determine if a client identifier is valid.
68       * @param mqttVersion The MQTT version semantics to use.
69       * @param maxClientIdLength The max client id length.
70       * @param clientId The client id value.
71       * @param acceptNulBytes MQTT normally does not allow NUL bytes in client identifiers.
72       * Set this to {@code true} to enable "legacy"/"lenient" mode, otherwise {@code false} for strict spec compliance.
73       * @return {@code true} if the client id is valid, otherwise {@code false}.
74       */
75      static boolean isValidClientId(MqttVersion mqttVersion, int maxClientIdLength, String clientId,
76                                     boolean acceptNulBytes) {
77          if (clientId == null || (!acceptNulBytes && clientId.indexOf('\0') != -1)) {
78              return false;
79          }
80          if (mqttVersion == MqttVersion.MQTT_3_1) {
81              return clientId.length() >= MIN_CLIENT_ID_LENGTH && clientId.length() <= maxClientIdLength;
82          }
83          if (mqttVersion == MqttVersion.MQTT_3_1_1 || mqttVersion == MqttVersion.MQTT_5) {
84              // In 3.1.3.1 Client Identifier of MQTT 3.1.1 and 5.0 specifications, The Server MAY allow ClientId’s
85              // that contain more than 23 encoded bytes. And, The Server MAY allow zero-length ClientId.
86              return true;
87          }
88          throw new IllegalArgumentException(mqttVersion + " is unknown mqtt version");
89      }
90  
91      static MqttFixedHeader validateFixedHeader(ChannelHandlerContext ctx, MqttFixedHeader mqttFixedHeader) {
92          switch (mqttFixedHeader.messageType()) {
93              case PUBREL:
94              case SUBSCRIBE:
95              case UNSUBSCRIBE:
96                  if (mqttFixedHeader.qosLevel() != MqttQoS.AT_LEAST_ONCE) {
97                      throw new DecoderException(mqttFixedHeader.messageType().name() + " message must have QoS 1");
98                  }
99                  return mqttFixedHeader;
100             case AUTH:
101                 if (MqttCodecUtil.getMqttVersion(ctx) != MqttVersion.MQTT_5) {
102                     throw new DecoderException("AUTH message requires at least MQTT 5");
103                 }
104                 return mqttFixedHeader;
105             default:
106                 return mqttFixedHeader;
107         }
108     }
109 
110     static MqttFixedHeader resetUnusedFields(MqttFixedHeader mqttFixedHeader) {
111         switch (mqttFixedHeader.messageType()) {
112             case CONNECT:
113             case CONNACK:
114             case PUBACK:
115             case PUBREC:
116             case PUBCOMP:
117             case SUBACK:
118             case UNSUBACK:
119             case PINGREQ:
120             case PINGRESP:
121             case DISCONNECT:
122                 if (mqttFixedHeader.isDup() ||
123                         mqttFixedHeader.qosLevel() != MqttQoS.AT_MOST_ONCE ||
124                         mqttFixedHeader.isRetain()) {
125                     return new MqttFixedHeader(
126                             mqttFixedHeader.messageType(),
127                             false,
128                             MqttQoS.AT_MOST_ONCE,
129                             false,
130                             mqttFixedHeader.remainingLength());
131                 }
132                 return mqttFixedHeader;
133             case PUBREL:
134             case SUBSCRIBE:
135             case UNSUBSCRIBE:
136                 if (mqttFixedHeader.isRetain()) {
137                     return new MqttFixedHeader(
138                             mqttFixedHeader.messageType(),
139                             mqttFixedHeader.isDup(),
140                             mqttFixedHeader.qosLevel(),
141                             false,
142                             mqttFixedHeader.remainingLength());
143                 }
144                 return mqttFixedHeader;
145             default:
146                 return mqttFixedHeader;
147         }
148     }
149 
150     private MqttCodecUtil() { }
151 }