/** * 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 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); } }
protected void directSend( ClientSession clientsession, String topic, AbstractMessage.QOSType qos, ByteBuffer message, boolean retained, Integer messageID) { String clientId = clientsession.clientID; LOG.debug( "directSend invoked clientId <{}> on topic <{}> QoS {} retained {} messageID {}", clientId, topic, qos, retained, messageID); PublishMessage pubMessage = new PublishMessage(); pubMessage.setRetainFlag(retained); pubMessage.setTopicName(topic); pubMessage.setQos(qos); pubMessage.setPayload(message); LOG.info("send publish message to <{}> on topic <{}>", clientId, topic); if (LOG.isDebugEnabled()) { LOG.debug("content <{}>", DebugUtils.payload2Str(message)); } // set the PacketIdentifier only for QoS > 0 if (pubMessage.getQos() != AbstractMessage.QOSType.MOST_ONE) { pubMessage.setMessageID(messageID); } else { if (messageID != null) { throw new RuntimeException( "Internal bad error, trying to forwardPublish a QoS 0 message with PacketIdentifier: " + messageID); } } if (m_clientIDs == null) { throw new RuntimeException( "Internal bad error, found m_clientIDs to null while it should be initialized, somewhere it's overwritten!!"); } LOG.debug("clientIDs are {}", m_clientIDs); if (m_clientIDs.get(clientId) == null) { // TODO while we were publishing to the target client, that client disconnected, // could happen is not an error HANDLE IT throw new RuntimeException( String.format( "Can't find a ConnectionDescriptor for client <%s> in cache <%s>", clientId, m_clientIDs)); } ServerChannel session = m_clientIDs.get(clientId).session; LOG.debug("Session for clientId {} is {}", clientId, session); String user = (String) session.getAttribute(NettyChannel.ATTR_KEY_USERNAME); if (!m_authorizator.canRead(topic, user, clientId)) { LOG.debug("topic {} doesn't have read credentials", topic); return; } session.write(pubMessage); }
public void processPublish(ServerChannel session, PublishMessage msg) { LOG.trace("PUB --PUBLISH--> SRV executePublish invoked with {}", msg); String clientID = (String) session.getAttribute(NettyChannel.ATTR_KEY_CLIENTID); final String topic = msg.getTopicName(); // check if the topic can be wrote String user = (String) session.getAttribute(NettyChannel.ATTR_KEY_USERNAME); if (!m_authorizator.canWrite(topic, user, clientID)) { LOG.debug("topic {} doesn't have write credentials", topic); return; } final AbstractMessage.QOSType qos = msg.getQos(); final Integer messageID = msg.getMessageID(); LOG.info("PUBLISH from clientID <{}> on topic <{}> with QoS {}", clientID, topic, qos); String guid = null; IMessagesStore.StoredMessage toStoreMsg = asStoredMessage(msg); toStoreMsg.setClientID(clientID); if (qos == AbstractMessage.QOSType.MOST_ONE) { // QoS0 route2Subscribers(toStoreMsg); } else if (qos == AbstractMessage.QOSType.LEAST_ONE) { // QoS1 route2Subscribers(toStoreMsg); sendPubAck(clientID, messageID); LOG.debug("replying with PubAck to MSG ID {}", messageID); } else if (qos == AbstractMessage.QOSType.EXACTLY_ONCE) { // QoS2 guid = m_messagesStore.storePublishForFuture(toStoreMsg); sendPubRec(clientID, messageID); // Next the client will send us a pub rel // NB publish to subscribers for QoS 2 happen upon PUBREL from publisher } if (msg.isRetainFlag()) { if (qos == AbstractMessage.QOSType.MOST_ONE) { // QoS == 0 && retain => clean old retained m_messagesStore.cleanRetained(topic); } else { if (!msg.getPayload().hasRemaining()) { m_messagesStore.cleanRetained(topic); } else { if (guid == null) { // before wasn't stored guid = m_messagesStore.storePublishForFuture(toStoreMsg); } m_messagesStore.storeRetained(topic, guid); } } } m_interceptor.notifyTopicPublished(msg, 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); }
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 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); }
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); }
/** * 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 failedCredentials(ServerChannel session) { ConnAckMessage okResp = new ConnAckMessage(); okResp.setReturnCode(ConnAckMessage.BAD_USERNAME_OR_PASSWORD); session.write(okResp); session.close(false); }
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"); }