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      private static final char[] TOPIC_WILDCARDS = {'#', '+'};
29  
30      static final AttributeKey<MqttVersion> MQTT_VERSION_KEY = AttributeKey.valueOf("NETTY_CODEC_MQTT_VERSION");
31  
32      static MqttVersion getMqttVersion(ChannelHandlerContext ctx) {
33          Attribute<MqttVersion> attr = ctx.channel().attr(MQTT_VERSION_KEY);
34          MqttVersion version = attr.get();
35          if (version == null) {
36              return MqttVersion.MQTT_3_1_1;
37          }
38          return version;
39      }
40  
41      static void setMqttVersion(ChannelHandlerContext ctx, MqttVersion version) {
42          Attribute<MqttVersion> attr = ctx.channel().attr(MQTT_VERSION_KEY);
43          attr.set(version);
44      }
45  
46      static boolean isValidPublishTopicName(String topicName) {
47          // publish topic name must not contain any wildcard
48          for (char c : TOPIC_WILDCARDS) {
49              if (topicName.indexOf(c) >= 0) {
50                  return false;
51              }
52          }
53          return true;
54      }
55  
56      static boolean isValidMessageId(int messageId) {
57          return messageId != 0;
58      }
59  
60      static boolean isValidClientId(MqttVersion mqttVersion, int maxClientIdLength, String clientId) {
61          if (mqttVersion == MqttVersion.MQTT_3_1) {
62              return clientId != null && clientId.length() >= MIN_CLIENT_ID_LENGTH &&
63                  clientId.length() <= maxClientIdLength;
64          }
65          if (mqttVersion == MqttVersion.MQTT_3_1_1 || mqttVersion == MqttVersion.MQTT_5) {
66              // In 3.1.3.1 Client Identifier of MQTT 3.1.1 and 5.0 specifications, The Server MAY allow ClientId’s
67              // that contain more than 23 encoded bytes. And, The Server MAY allow zero-length ClientId.
68              return clientId != null;
69          }
70          throw new IllegalArgumentException(mqttVersion + " is unknown mqtt version");
71      }
72  
73      static MqttFixedHeader validateFixedHeader(ChannelHandlerContext ctx, MqttFixedHeader mqttFixedHeader) {
74          switch (mqttFixedHeader.messageType()) {
75              case PUBREL:
76              case SUBSCRIBE:
77              case UNSUBSCRIBE:
78                  if (mqttFixedHeader.qosLevel() != MqttQoS.AT_LEAST_ONCE) {
79                      throw new DecoderException(mqttFixedHeader.messageType().name() + " message must have QoS 1");
80                  }
81                  return mqttFixedHeader;
82              case AUTH:
83                  if (MqttCodecUtil.getMqttVersion(ctx) != MqttVersion.MQTT_5) {
84                      throw new DecoderException("AUTH message requires at least MQTT 5");
85                  }
86                  return mqttFixedHeader;
87              default:
88                  return mqttFixedHeader;
89          }
90      }
91  
92      static MqttFixedHeader resetUnusedFields(MqttFixedHeader mqttFixedHeader) {
93          switch (mqttFixedHeader.messageType()) {
94              case CONNECT:
95              case CONNACK:
96              case PUBACK:
97              case PUBREC:
98              case PUBCOMP:
99              case SUBACK:
100             case UNSUBACK:
101             case PINGREQ:
102             case PINGRESP:
103             case DISCONNECT:
104                 if (mqttFixedHeader.isDup() ||
105                         mqttFixedHeader.qosLevel() != MqttQoS.AT_MOST_ONCE ||
106                         mqttFixedHeader.isRetain()) {
107                     return new MqttFixedHeader(
108                             mqttFixedHeader.messageType(),
109                             false,
110                             MqttQoS.AT_MOST_ONCE,
111                             false,
112                             mqttFixedHeader.remainingLength());
113                 }
114                 return mqttFixedHeader;
115             case PUBREL:
116             case SUBSCRIBE:
117             case UNSUBSCRIBE:
118                 if (mqttFixedHeader.isRetain()) {
119                     return new MqttFixedHeader(
120                             mqttFixedHeader.messageType(),
121                             mqttFixedHeader.isDup(),
122                             mqttFixedHeader.qosLevel(),
123                             false,
124                             mqttFixedHeader.remainingLength());
125                 }
126                 return mqttFixedHeader;
127             default:
128                 return mqttFixedHeader;
129         }
130     }
131 
132     private MqttCodecUtil() { }
133 }