content: /**
   * Licensed to the Apache Software Foundation (ASF) under one or more
   * contributor license agreements.  See the NOTICE file distributed with
   * this work for additional information regarding copyright ownership.
   * The ASF 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 org.apache.activemq.transport.mqtt;
  
  import java.io.UnsupportedEncodingException;
  
  import org.fusesource.mqtt.codec.CONNECT;
  import org.fusesource.mqtt.codec.DISCONNECT;
  import org.fusesource.mqtt.codec.PINGREQ;
  import org.fusesource.mqtt.codec.PUBACK;
  import org.fusesource.mqtt.codec.PUBCOMP;
  import org.fusesource.mqtt.codec.PUBLISH;
  import org.fusesource.mqtt.codec.PUBREC;
  import org.fusesource.mqtt.codec.PUBREL;
  import org.fusesource.mqtt.codec.SUBSCRIBE;
  import org.fusesource.mqtt.codec.UNSUBSCRIBE;
  
  /**
   * A set of static methods useful for handling MQTT based client connections.
   */
  public class MQTTProtocolSupport {
  
      private static final int TOPIC_NAME_MIN_LENGTH = 1;
      private static final int TOPIC_NAME_MAX_LENGTH = 65535;
  
      private static final String MULTI_LEVEL_WILDCARD = "#";
      private static final String SINGLE_LEVEL_WILDCARD = "+";
  
      private static final char MULTI_LEVEL_WILDCARD_CHAR = '#';
      private static final char SINGLE_LEVEL_WILDCARD_CHAR = '+';
      private static final char TOPIC_LEVEL_SEPERATOR_CHAR = '/';
  
      /**
       * Converts an MQTT formatted Topic name into a suitable ActiveMQ Destination
       * name string.
       *
       * @param name
       *        the MQTT formatted topic name.
       *
       * @return an destination name that fits the ActiveMQ conventions.
       */
      public static String convertMQTTToActiveMQ(String name) {
          char[] chars = name.toCharArray();
          for (int i = 0; i < chars.length; i++) {
              switch(chars[i]) {
                  case '#':
                      chars[i] = '>';
                      break;
                  case '>':
                      chars[i] = '#';
                      break;
                  case '+':
                      chars[i] = '*';
                      break;
                  case '*':
                      chars[i] = '+';
                      break;
                  case '/':
                      chars[i] = '.';
                      break;
                  case '.':
                      chars[i] = '/';
                      break;
              }
          }
          String rc = new String(chars);
          return rc;
      }
  
      /**
       * Converts an ActiveMQ destination name into a correctly formatted
       * MQTT destination name.
       *
       * @param destinationName
       *        the ActiveMQ destination name to process.
       *
       * @return a destination name formatted for MQTT.
       */
      public static String convertActiveMQToMQTT(String destinationName) {
          char[] chars = destinationName.toCharArray();
          for (int i = 0; i < chars.length; i++) {
              switch(chars[i]) {
                  case '>':
                      chars[i] = '#';
                      break;
                  case '#':
                      chars[i] = '>';
                      break;
                  case '*':
                      chars[i] = '+';
                      break;
                  case '+':
                      chars[i] = '*';
                      break;
                  case '.':
                      chars[i] = '/';
                      break;
                  case '/':
                      chars[i] = '.';
                      break;
              }
          }
          String rc = new String(chars);
          return rc;
      }
  
      /**
       * Given an MQTT header byte, determine the command type that the header
       * represents.
       *
       * @param header
       *        the byte value for the MQTT frame header.
       *
       * @return a string value for the given command type.
       */
      public static String commandType(byte header) {
          byte messageType = (byte) ((header & 0xF0) >>> 4);
          switch (messageType) {
              case PINGREQ.TYPE:
                  return "PINGREQ";
              case CONNECT.TYPE:
                  return "CONNECT";
              case DISCONNECT.TYPE:
                  return "DISCONNECT";
              case SUBSCRIBE.TYPE:
                  return "SUBSCRIBE";
              case UNSUBSCRIBE.TYPE:
                  return "UNSUBSCRIBE";
              case PUBLISH.TYPE:
                  return "PUBLISH";
              case PUBACK.TYPE:
                  return "PUBACK";
              case PUBREC.TYPE:
                  return "PUBREC";
              case PUBREL.TYPE:
                  return "PUBREL";
              case PUBCOMP.TYPE:
                  return "PUBCOMP";
              default:
                  return "UNKNOWN";
          }
      }
  
      /**
       * Validate that the Topic names given by client commands are valid
       * based on the MQTT protocol specification.
       *
       * @param topicName
       *      the given Topic name provided by the client.
       *
       * @throws MQTTProtocolException if the value given is invalid.
       */
      public static void validate(String topicName) throws MQTTProtocolException {
          int topicLen = 0;
          try {
              topicLen = topicName.getBytes("UTF-8").length;
          } catch (UnsupportedEncodingException e) {
              throw new MQTTProtocolException("Topic name contained invalid UTF-8 encoding.");
          }
  
          // Spec: Unless stated otherwise all UTF-8 encoded strings can have any length in
          //       the range 0 to 65535 bytes.
          if (topicLen < TOPIC_NAME_MIN_LENGTH || topicLen > TOPIC_NAME_MAX_LENGTH) {
              throw new MQTTProtocolException("Topic name given had invliad length.");
          }
  
          // 4.7.1.2 and 4.7.1.3 these can stand alone
          if (MULTI_LEVEL_WILDCARD.equals(topicName) || SINGLE_LEVEL_WILDCARD.equals(topicName)) {
              return;
          }
  
          // Spec: 4.7.1.2
          //  The multi-level wildcard character MUST be specified either on its own or following a
          //  topic level separator. In either case it MUST be the last character specified in the
          //  Topic Filter [MQTT-4.7.1-2].
          int numWildCards = 0;
          for (int i = 0; i < topicName.length(); ++i) {
              if (topicName.charAt(i) == MULTI_LEVEL_WILDCARD_CHAR) {
                  numWildCards++;
  
                  // If prev exists it must be a separator
                  if (i > 0 && topicName.charAt(i - 1) != TOPIC_LEVEL_SEPERATOR_CHAR) {
                      throw new MQTTProtocolException("The multi level wildcard must stand alone: " + topicName);
                  }
              }
  
              if (numWildCards > 1) {
                  throw new MQTTProtocolException("Topic Filter can only have one multi-level filter: " + topicName);
              }
          }
  
          if (topicName.contains(MULTI_LEVEL_WILDCARD) && !topicName.endsWith(MULTI_LEVEL_WILDCARD)) {
              throw new MQTTProtocolException("The multi-level filter must be at the end of the Topic name: " + topicName);
          }
  
          // Spec: 4.7.1.3
          // The single-level wildcard can be used at any level in the Topic Filter, including
          // first and last levels. Where it is used it MUST occupy an entire level of the filter
          //
          // [MQTT-4.7.1-3]. It can be used at more than one level in the Topic Filter and can be
          // used in conjunction with the multilevel wildcard.
          for (int i = 0; i < topicName.length(); ++i) {
              if (topicName.charAt(i) != SINGLE_LEVEL_WILDCARD_CHAR) {
                  continue;
              }
  
              // If prev exists it must be a separator
              if (i > 0 && topicName.charAt(i - 1) != TOPIC_LEVEL_SEPERATOR_CHAR) {
                  throw new MQTTProtocolException("The single level wildcard must stand alone: " + topicName);
              }
  
              // If next exists it must be a separator
              if (i < topicName.length() - 1 && topicName.charAt(i + 1) != TOPIC_LEVEL_SEPERATOR_CHAR) {
                  throw new MQTTProtocolException("The single level wildcard must stand alone: " + topicName);
              }
          }
      }
  }
  