public void processSubscribe(ServerChannel session, SubscribeMessage msg) {
    String clientID = (String) session.getAttribute(NettyChannel.ATTR_KEY_CLIENTID);
    LOG.debug("SUBSCRIBE client <{}> packetID {}", clientID, msg.getMessageID());

    ClientSession clientSession = m_sessionsStore.sessionForClient(clientID);
    verifyToActivate(clientID, clientSession);
    // ack the client
    SubAckMessage ackMessage = new SubAckMessage();
    ackMessage.setMessageID(msg.getMessageID());

    List<Subscription> newSubscriptions = new ArrayList<>();
    for (SubscribeMessage.Couple req : msg.subscriptions()) {
      AbstractMessage.QOSType qos = AbstractMessage.QOSType.valueOf(req.getQos());
      Subscription newSubscription = new Subscription(clientID, req.getTopicFilter(), qos);
      // boolean valid = subscribeSingleTopic(newSubscription, req.getTopicFilter());
      boolean valid = clientSession.subscribe(req.getTopicFilter(), newSubscription);
      ackMessage.addType(valid ? qos : AbstractMessage.QOSType.FAILURE);
      if (valid) {
        newSubscriptions.add(newSubscription);
      }
    }

    // save session, persist subscriptions from session
    LOG.debug("SUBACK for packetID {}", msg.getMessageID());
    if (LOG.isTraceEnabled()) {
      LOG.trace("subscription tree {}", subscriptions.dumpTree());
    }
    session.write(ackMessage);

    // fire the publish
    for (Subscription subscription : newSubscriptions) {
      subscribeSingleTopic(subscription);
    }
  }
  /**
   * Remove the clientID from topic subscription, if not previously subscribed, doesn't reply any
   * error
   */
  public void processUnsubscribe(ServerChannel session, UnsubscribeMessage msg) {
    List<String> topics = msg.topicFilters();
    int messageID = msg.getMessageID();
    String clientID = (String) session.getAttribute(NettyChannel.ATTR_KEY_CLIENTID);
    LOG.debug("UNSUBSCRIBE subscription on topics {} for clientID <{}>", topics, clientID);

    ClientSession clientSession = m_sessionsStore.sessionForClient(clientID);
    verifyToActivate(clientID, clientSession);
    for (String topic : topics) {
      boolean validTopic = SubscriptionsStore.validate(topic);
      if (!validTopic) {
        // close the connection, not valid topicFilter is a protocol violation
        session.close(true);
        LOG.warn(
            "UNSUBSCRIBE found an invalid topic filter <{}> for clientID <{}>", topic, clientID);
        return;
      }

      subscriptions.removeSubscription(topic, clientID);
      clientSession.unsubscribeFrom(topic);
      m_interceptor.notifyTopicUnsubscribed(topic, clientID);
    }

    // ack the client
    UnsubAckMessage ackMessage = new UnsubAckMessage();
    ackMessage.setMessageID(messageID);

    LOG.info("replying with UnsubAck to MSG ID {}", messageID);
    session.write(ackMessage);
  }
 public void processPubAck(ServerChannel session, PubAckMessage msg) {
   String clientID = (String) session.getAttribute(NettyChannel.ATTR_KEY_CLIENTID);
   int messageID = msg.getMessageID();
   // Remove the message from message store
   ClientSession targetSession = m_sessionsStore.sessionForClient(clientID);
   verifyToActivate(clientID, targetSession);
   targetSession.inFlightAcknowledged(messageID);
 }
 public void processPubComp(ServerChannel session, PubCompMessage msg) {
   String clientID = (String) session.getAttribute(NettyChannel.ATTR_KEY_CLIENTID);
   int messageID = msg.getMessageID();
   LOG.debug(
       "\t\tSRV <--PUBCOMP-- SUB processPubComp invoked for clientID {} ad messageID {}",
       clientID,
       messageID);
   // once received the PUBCOMP then remove the message from the temp memory
   ClientSession targetSession = m_sessionsStore.sessionForClient(clientID);
   verifyToActivate(clientID, targetSession);
   targetSession.secondPhaseAcknowledged(messageID);
 }
 public void processConnectionLost(String clientID, boolean sessionStolen) {
   // If already removed a disconnect message was already processed for this clientID
   if (sessionStolen && m_clientIDs.remove(clientID) != null) {
     // de-activate the subscriptions for this ClientID
     ClientSession clientSession = m_sessionsStore.sessionForClient(clientID);
     clientSession.deactivate();
     LOG.info("Lost connection with client <{}>", clientID);
   }
   // publish the Will message (if any) for the clientID
   if (!sessionStolen && m_willStore.containsKey(clientID)) {
     WillMessage will = m_willStore.get(clientID);
     forwardPublishWill(will, clientID);
     m_willStore.remove(clientID);
   }
 }
  public void processDisconnect(ServerChannel session) throws InterruptedException {
    String clientID = (String) session.getAttribute(NettyChannel.ATTR_KEY_CLIENTID);
    boolean cleanSession = (Boolean) session.getAttribute(NettyChannel.ATTR_KEY_CLEANSESSION);
    LOG.info("DISCONNECT client <{}> with clean session {}", clientID, cleanSession);
    ClientSession clientSession = m_sessionsStore.sessionForClient(clientID);
    clientSession.disconnect();

    m_clientIDs.remove(clientID);
    session.close(true);

    // cleanup the will store
    m_willStore.remove(clientID);

    m_interceptor.notifyClientDisconnected(clientID);
    LOG.info("DISCONNECT client <{}> finished", clientID, cleanSession);
  }
  /**
   * Flood the subscribers with the message to notify. MessageID is optional and should only used
   * for QoS 1 and 2
   */
  void route2Subscribers(IMessagesStore.StoredMessage pubMsg) {
    final String topic = pubMsg.getTopic();
    final AbstractMessage.QOSType publishingQos = pubMsg.getQos();
    final ByteBuffer origMessage = pubMsg.getMessage();
    LOG.debug(
        "route2Subscribers republishing to existing subscribers that matches the topic {}", topic);
    if (LOG.isTraceEnabled()) {
      LOG.trace("content <{}>", DebugUtils.payload2Str(origMessage));
      LOG.trace("subscription tree {}", subscriptions.dumpTree());
    }
    // if QoS 1 or 2 store the message
    String guid = null;
    if (publishingQos == QOSType.EXACTLY_ONCE || publishingQos == QOSType.LEAST_ONE) {
      guid = m_messagesStore.storePublishForFuture(pubMsg);
    }

    for (final Subscription sub : subscriptions.matches(topic)) {
      AbstractMessage.QOSType qos = publishingQos;
      if (qos.byteValue() > sub.getRequestedQos().byteValue()) {
        qos = sub.getRequestedQos();
      }
      ClientSession targetSession = m_sessionsStore.sessionForClient(sub.getClientId());
      verifyToActivate(sub.getClientId(), targetSession);

      LOG.debug(
          "Broker republishing to client <{}> topic <{}> qos <{}>, active {}",
          sub.getClientId(),
          sub.getTopicFilter(),
          qos,
          targetSession.isActive());
      ByteBuffer message = origMessage.duplicate();
      if (qos == AbstractMessage.QOSType.MOST_ONE && targetSession.isActive()) {
        // QoS 0
        directSend(targetSession, topic, qos, message, false, null);
      } else {
        // QoS 1 or 2
        // if the target subscription is not clean session and is not connected => store it
        if (!targetSession.isCleanSession() && !targetSession.isActive()) {
          // store the message in targetSession queue to deliver
          targetSession.enqueueToDeliver(guid);
        } else {
          // publish
          if (targetSession.isActive()) {
            int messageId = targetSession.nextPacketId();
            targetSession.inFlightAckWaiting(guid, messageId);
            directSend(targetSession, topic, qos, message, false, messageId);
          }
        }
      }
    }
  }
  public void processPubRec(ServerChannel session, PubRecMessage msg) {
    String clientID = (String) session.getAttribute(NettyChannel.ATTR_KEY_CLIENTID);
    int messageID = msg.getMessageID();
    ClientSession targetSession = m_sessionsStore.sessionForClient(clientID);
    verifyToActivate(clientID, targetSession);
    // remove from the inflight and move to the QoS2 second phase queue
    targetSession.inFlightAcknowledged(messageID);
    targetSession.secondPhaseAckWaiting(messageID);
    // once received a PUBREC reply with a PUBREL(messageID)
    LOG.debug(
        "\t\tSRV <--PUBREC-- SUB processPubRec invoked for clientID {} ad messageID {}",
        clientID,
        messageID);
    PubRelMessage pubRelMessage = new PubRelMessage();
    pubRelMessage.setMessageID(messageID);
    pubRelMessage.setQos(AbstractMessage.QOSType.LEAST_ONE);

    session.write(pubRelMessage);
  }
  /** Republish QoS1 and QoS2 messages stored into the session for the clientID. */
  private void republishStoredInSession(ClientSession clientSession) {
    LOG.trace("republishStoredInSession for client <{}>", clientSession);
    List<IMessagesStore.StoredMessage> publishedEvents = clientSession.storedMessages();
    if (publishedEvents.isEmpty()) {
      LOG.info("No stored messages for client <{}>", clientSession.clientID);
      return;
    }

    LOG.info("republishing stored messages to client <{}>", clientSession.clientID);
    for (IMessagesStore.StoredMessage pubEvt : publishedEvents) {
      // TODO put in flight zone
      directSend(
          clientSession,
          pubEvt.getTopic(),
          pubEvt.getQos(),
          pubEvt.getMessage(),
          false,
          pubEvt.getMessageID());
      clientSession.removeEnqueued(pubEvt.getGuid());
    }
  }
  /**
   * Second phase of a publish QoS2 protocol, sent by publisher to the broker. Search the stored
   * message and publish to all interested subscribers.
   */
  public void processPubRel(ServerChannel session, PubRelMessage msg) {
    String clientID = (String) session.getAttribute(NettyChannel.ATTR_KEY_CLIENTID);
    int messageID = msg.getMessageID();
    LOG.debug(
        "PUB --PUBREL--> SRV processPubRel invoked for clientID {} ad messageID {}",
        clientID,
        messageID);
    ClientSession targetSession = m_sessionsStore.sessionForClient(clientID);
    verifyToActivate(clientID, targetSession);
    IMessagesStore.StoredMessage evt = targetSession.storedMessage(messageID);
    route2Subscribers(evt);

    if (evt.isRetained()) {
      final String topic = evt.getTopic();
      if (!evt.getMessage().hasRemaining()) {
        m_messagesStore.cleanRetained(topic);
      } else {
        m_messagesStore.storeRetained(topic, evt.getGuid());
      }
    }

    sendPubComp(clientID, messageID);
  }
 private void verifyToActivate(String clientID, ClientSession targetSession) {
   if (m_clientIDs.containsKey(clientID)) {
     targetSession.activate();
   }
 }
  public void processConnect(ServerChannel session, ConnectMessage msg) {
    LOG.debug("CONNECT for client <{}>", msg.getClientID());
    if (msg.getProtocolVersion() != VERSION_3_1 && msg.getProtocolVersion() != VERSION_3_1_1) {
      ConnAckMessage badProto = new ConnAckMessage();
      badProto.setReturnCode(ConnAckMessage.UNNACEPTABLE_PROTOCOL_VERSION);
      LOG.warn("processConnect sent bad proto ConnAck");
      session.write(badProto);
      session.close(false);
      return;
    }

    if (msg.getClientID() == null || msg.getClientID().length() == 0) {
      ConnAckMessage okResp = new ConnAckMessage();
      okResp.setReturnCode(ConnAckMessage.IDENTIFIER_REJECTED);
      session.write(okResp);
      m_interceptor.notifyClientConnected(msg);
      return;
    }

    // handle user authentication
    if (msg.isUserFlag()) {
      byte[] pwd = null;
      if (msg.isPasswordFlag()) {
        pwd = msg.getPassword();
      } else if (!this.allowAnonymous) {
        failedCredentials(session);
        return;
      }
      if (!m_authenticator.checkValid(msg.getUsername(), pwd)) {
        failedCredentials(session);
        return;
      }
      session.setAttribute(NettyChannel.ATTR_KEY_USERNAME, msg.getUsername());
    } else if (!this.allowAnonymous) {
      failedCredentials(session);
      return;
    }

    // if an old client with the same ID already exists close its session.
    if (m_clientIDs.containsKey(msg.getClientID())) {
      LOG.info(
          "Found an existing connection with same client ID <{}>, forcing to close",
          msg.getClientID());
      // clean the subscriptions if the old used a cleanSession = true
      ServerChannel oldSession = m_clientIDs.get(msg.getClientID()).session;
      ClientSession oldClientSession = m_sessionsStore.sessionForClient(msg.getClientID());
      oldClientSession.disconnect();
      oldSession.setAttribute(NettyChannel.ATTR_KEY_SESSION_STOLEN, true);
      oldSession.close(false);
      LOG.debug("Existing connection with same client ID <{}>, forced to close", msg.getClientID());
    }

    ConnectionDescriptor connDescr =
        new ConnectionDescriptor(msg.getClientID(), session, msg.isCleanSession());
    m_clientIDs.put(msg.getClientID(), connDescr);

    int keepAlive = msg.getKeepAlive();
    LOG.debug("Connect with keepAlive {} s", keepAlive);
    session.setAttribute(NettyChannel.ATTR_KEY_KEEPALIVE, keepAlive);
    session.setAttribute(NettyChannel.ATTR_KEY_CLEANSESSION, msg.isCleanSession());
    // used to track the client in the subscription and publishing phases.
    session.setAttribute(NettyChannel.ATTR_KEY_CLIENTID, msg.getClientID());
    LOG.debug("Connect create session <{}>", session);

    session.setIdleTime(Math.round(keepAlive * 1.5f));

    // Handle will flag
    if (msg.isWillFlag()) {
      AbstractMessage.QOSType willQos = AbstractMessage.QOSType.valueOf(msg.getWillQos());
      byte[] willPayload = msg.getWillMessage();
      ByteBuffer bb = (ByteBuffer) ByteBuffer.allocate(willPayload.length).put(willPayload).flip();
      // save the will testament in the clientID store
      WillMessage will = new WillMessage(msg.getWillTopic(), bb, msg.isWillRetain(), willQos);
      m_willStore.put(msg.getClientID(), will);
    }

    ConnAckMessage okResp = new ConnAckMessage();
    okResp.setReturnCode(ConnAckMessage.CONNECTION_ACCEPTED);

    ClientSession clientSession = m_sessionsStore.sessionForClient(msg.getClientID());
    boolean isSessionAlreadyStored = clientSession != null;
    if (!msg.isCleanSession() && isSessionAlreadyStored) {
      okResp.setSessionPresent(true);
    }
    session.write(okResp);
    m_interceptor.notifyClientConnected(msg);

    if (!isSessionAlreadyStored) {
      LOG.info("Create persistent session for clientID <{}>", msg.getClientID());
      clientSession = m_sessionsStore.createNewSession(msg.getClientID(), msg.isCleanSession());
    }
    clientSession.activate();
    if (msg.isCleanSession()) {
      clientSession.cleanSession();
    }
    LOG.info(
        "Connected client ID <{}> with clean session {}", msg.getClientID(), msg.isCleanSession());
    if (!msg.isCleanSession()) {
      // force the republish of stored QoS1 and QoS2
      republishStoredInSession(clientSession);
    }
    LOG.info("CONNECT processed");
  }