예제 #1
0
파일: broker.py 프로젝트: narry/hbmqtt
 def _broadcast_loop(self):
     running_tasks = deque()
     try:
         while True:
             while running_tasks and running_tasks[0].done():
                 running_tasks.popleft()
             broadcast = yield from self._broadcast_queue.get()
             if self.logger.isEnabledFor(logging.DEBUG):
                 self.logger.debug("broadcasting %r" % broadcast)
             for k_filter in self._subscriptions:
                 if self.matches(broadcast['topic'], k_filter):
                     subscriptions = self._subscriptions[k_filter]
                     for (target_session, qos) in subscriptions:
                         if 'qos' in broadcast:
                             qos = broadcast['qos']
                         if target_session.transitions.state == 'connected':
                             self.logger.debug("broadcasting application message from %s on topic '%s' to %s" %
                                               (format_client_message(session=broadcast['session']),
                                                broadcast['topic'], format_client_message(session=target_session)))
                             handler = self._get_handler(target_session)
                             task = ensure_future(
                                 handler.mqtt_publish(broadcast['topic'], broadcast['data'], qos, retain=False),
                                 loop=self._loop)
                             running_tasks.append(task)
                         else:
                             self.logger.debug("retaining application message from %s on topic '%s' to client '%s'" %
                                               (format_client_message(session=broadcast['session']),
                                                broadcast['topic'], format_client_message(session=target_session)))
                             retained_message = RetainedApplicationMessage(
                                 broadcast['session'], broadcast['topic'], broadcast['data'], qos)
                             yield from target_session.retained_messages.put(retained_message)
     except CancelledError:
         # Wait until current broadcasting tasks end
         if running_tasks:
             yield from asyncio.wait(running_tasks, loop=self._loop)
예제 #2
0
파일: broker.py 프로젝트: hexdump42/hbmqtt
    def broadcast_application_message(self, source_session, topic, data, force_qos=None):
        #self.logger.debug("Broadcasting message from %s on topic %s" %
        #                  (format_client_message(session=source_session), topic)
        #                  )
        publish_tasks = []
        try:
            for k_filter in self._subscriptions:
                if self.matches(topic, k_filter):
                    subscriptions = self._subscriptions[k_filter]
                    for subscription in subscriptions:
                        target_session = subscription.session
                        qos = subscription.qos
                        if force_qos is not None:
                            qos = force_qos
                        if target_session.transitions.state == 'connected':
                            self.logger.debug("broadcasting application message from %s on topic '%s' to %s" %
                                              (format_client_message(session=source_session),
                                               topic, format_client_message(session=target_session)))
                            handler = subscription.session.handler
                            publish_tasks.append(
                                asyncio.Task(handler.mqtt_publish(topic, data, qos, retain=False))
                            )
                        else:
                            self.logger.debug("retaining application message from %s on topic '%s' to client '%s'" %
                                              (format_client_message(session=source_session),
                                               topic, format_client_message(session=target_session)))
                            retained_message = RetainedApplicationMessage(source_session, topic, data, qos)
                            publish_tasks.append(
                                asyncio.Task(target_session.retained_messages.put(retained_message))
                            )

            if len(publish_tasks) > 0:
                asyncio.wait(publish_tasks)
        except Exception as e:
            self.logger.warn("Message broadcasting failed: %s", e)
예제 #3
0
 def add_subscription(self, subscription, session):
     try:
         a_filter = subscription[0]
         if '#' in a_filter and not a_filter.endswith('#'):
             # [MQTT-4.7.1-2] Wildcard character '#' is only allowed as last character in filter
             return 0x80
         if a_filter != "+":
             if '+' in a_filter:
                 if "/+" not in a_filter and "+/" not in a_filter:
                     # [MQTT-4.7.1-3] + wildcard character must occupy entire level
                     return 0x80
         # Check if the client is authorised to connect to the topic
         permitted = yield from self.topic_filtering(session, topic=a_filter)
         if not permitted:
             return 0x80
         qos = subscription[1]
         if 'max-qos' in self.config and qos > self.config['max-qos']:
             qos = self.config['max-qos']
         if a_filter not in self._subscriptions:
             self._subscriptions[a_filter] = []
         already_subscribed = next(
             (s for (s, qos) in self._subscriptions[a_filter] if s.client_id == session.client_id), None)
         if not already_subscribed:
             self._subscriptions[a_filter].append((session, qos))
         else:
             self.logger.debug("Client %s has already subscribed to %s" % (format_client_message(session=session), a_filter))
         return qos
     except KeyError:
         return 0x80
예제 #4
0
 def handle_connect(self, connect: ConnectPacket):
     # Broker handler shouldn't received CONNECT message during messages handling
     # as CONNECT messages are managed by the broker on client connection
     self.logger.error('%s [MQTT-3.1.0-2] %s : CONNECT message received during messages handling' %
                       (self.session.client_id, format_client_message(self.session)))
     if self._disconnect_waiter is not None and not self._disconnect_waiter.done():
         self._disconnect_waiter.set_result(None)
예제 #5
0
파일: broker.py 프로젝트: xbee/hbmqtt
 def add_subscription(self, subscription, session):
     try:
         a_filter = subscription[0]
         if '#' in a_filter and not a_filter.endswith('#'):
             # [MQTT-4.7.1-2] Wildcard character '#' is only allowed as last character in filter
             return 0x80
         if a_filter != "+":
             if '+' in a_filter:
                 if "/+" not in a_filter and "+/" not in a_filter:
                     # [MQTT-4.7.1-3] + wildcard character must occupy entire level
                     return 0x80
         # Check if the client is authorised to connect to the topic
         permitted = yield from self.topic_filtering(session,
                                                     topic=a_filter)
         if not permitted:
             return 0x80
         qos = subscription[1]
         if 'max-qos' in self.config and qos > self.config['max-qos']:
             qos = self.config['max-qos']
         if a_filter not in self._subscriptions:
             self._subscriptions[a_filter] = []
         already_subscribed = next(
             (s for (s, qos) in self._subscriptions[a_filter]
              if s.client_id == session.client_id), None)
         if not already_subscribed:
             self._subscriptions[a_filter].append((session, qos))
         else:
             self.logger.debug(
                 "Client %s has already subscribed to %s" %
                 (format_client_message(session=session), a_filter))
         return qos
     except KeyError:
         return 0x80
예제 #6
0
파일: broker.py 프로젝트: hexdump42/hbmqtt
 def publish_retained_messages_for_subscription(self, subscription, session):
     self.logger.debug("Begin broadcasting messages retained due to subscription on '%s' from %s" %
                       (subscription['filter'], format_client_message(session=session)))
     publish_tasks = []
     for d_topic in self._global_retained_messages:
         self.logger.debug("matching : %s %s" % (d_topic, subscription['filter']))
         if self.matches(d_topic, subscription['filter']):
             self.logger.debug("%s and %s match" % (d_topic, subscription['filter']))
             retained = self._global_retained_messages[d_topic]
             publish_tasks.append(asyncio.Task(
                 session.handler.mqtt_publish(
                     retained.topic, retained.data, subscription['qos'], True)))
     if len(publish_tasks) > 0:
         asyncio.wait(publish_tasks)
     self.logger.debug("End broadcasting messages retained due to subscription on '%s' from %s" %
                       (subscription['filter'], format_client_message(session=session)))
예제 #7
0
파일: broker.py 프로젝트: hexdump42/hbmqtt
    def add_subscription(self, subscription, session):
        import re
        wildcard_pattern = re.compile('.*?/?\+/?.*?')
        try:
            a_filter = subscription['filter']
            if '#' in a_filter and not a_filter.endswith('#'):
                # [MQTT-4.7.1-2] Wildcard character '#' is only allowed as last character in filter
                return 0x80
            if '+' in a_filter and not wildcard_pattern.match(a_filter):
                # [MQTT-4.7.1-3] + wildcard character must occupy entire level
                return 0x80

            qos = subscription['qos']
            if 'max-qos' in self.config and qos > self.config['max-qos']:
                qos = self.config['max-qos']
            if a_filter not in self._subscriptions:
                self._subscriptions[a_filter] = []
            already_subscribed = next(
                (s for s in self._subscriptions[a_filter] if s.session.client_id == session.client_id), None)
            if not already_subscribed:
                self._subscriptions[a_filter].append(Subscription(session, qos))
            else:
                self.logger.debug("Client %s has already subscribed to %s" % (format_client_message(session=session), a_filter))
            return qos
        except KeyError:
            return 0x80
예제 #8
0
    def add_subscription(self, subscription, session):
        import re
        wildcard_pattern = re.compile('.*?/?\+/?.*?')
        try:
            a_filter = subscription[0]
            if '#' in a_filter and not a_filter.endswith('#'):
                # [MQTT-4.7.1-2] Wildcard character '#' is only allowed as last character in filter
                return 0x80
            if a_filter != "+":
                if '+' in a_filter:
                    if "/+" not in a_filter and "+/" not in a_filter:
                        # [MQTT-4.7.1-3] + wildcard character must occupy entire level
                        return 0x80

            qos = subscription[1]
            if 'max-qos' in self.config and qos > self.config['max-qos']:
                qos = self.config['max-qos']
            if a_filter not in self._subscriptions:
                self._subscriptions[a_filter] = []
            already_subscribed = next(
                (s for (s,qos) in self._subscriptions[a_filter] if s.client_id == session.client_id), None)
            if not already_subscribed:
                self._subscriptions[a_filter].append((session, qos))
            else:
                self.logger.debug("Client %s has already subscribed to %s" % (format_client_message(session=session), a_filter))
            return qos
        except KeyError:
            return 0x80
예제 #9
0
 def handle_connect(self, connect: ConnectPacket):
     # Broker handler shouldn't received CONNECT message during messages handling
     # as CONNECT messages are managed by the broker on client connection
     self.logger.error('%s [MQTT-3.1.0-2] %s : CONNECT message received during messages handling' %
                       (self.session.client_id, format_client_message(self.session)))
     if self._disconnect_waiter is not None and not self._disconnect_waiter.done():
         self._disconnect_waiter.set_result(None)
예제 #10
0
 def publish_retained_messages_for_subscription(self, subscription, session):
     self.logger.debug("Begin broadcasting messages retained due to subscription on '%s' from %s" %
                       (subscription[0], format_client_message(session=session)))
     publish_tasks = []
     handler = self._get_handler(session)
     for d_topic in self._retained_messages:
         self.logger.debug("matching : %s %s" % (d_topic, subscription[0]))
         if self.matches(d_topic, subscription[0]):
             self.logger.debug("%s and %s match" % (d_topic, subscription[0]))
             retained = self._retained_messages[d_topic]
             publish_tasks.append(asyncio.Task(
                 handler.mqtt_publish(
                     retained.topic, retained.data, subscription[1], True), loop=self._loop))
     if publish_tasks:
         yield from asyncio.wait(publish_tasks, loop=self._loop)
     self.logger.debug("End broadcasting messages retained due to subscription on '%s' from %s" %
                       (subscription[0], format_client_message(session=session)))
예제 #11
0
파일: broker.py 프로젝트: narry/hbmqtt
 def publish_retained_messages_for_subscription(self, subscription, session):
     self.logger.debug("Begin broadcasting messages retained due to subscription on '%s' from %s" %
                       (subscription[0], format_client_message(session=session)))
     publish_tasks = []
     handler = self._get_handler(session)
     for d_topic in self._retained_messages:
         self.logger.debug("matching : %s %s" % (d_topic, subscription[0]))
         if self.matches(d_topic, subscription[0]):
             self.logger.debug("%s and %s match" % (d_topic, subscription[0]))
             retained = self._retained_messages[d_topic]
             publish_tasks.append(asyncio.Task(
                 handler.mqtt_publish(
                     retained.topic, retained.data, subscription[1], True), loop=self._loop))
     if publish_tasks:
         yield from asyncio.wait(publish_tasks, loop=self._loop)
     self.logger.debug("End broadcasting messages retained due to subscription on '%s' from %s" %
                       (subscription[0], format_client_message(session=session)))
예제 #12
0
파일: broker.py 프로젝트: smurfix/hbmqtt
 async def _broadcast_loop(self):
     async with anyio.create_task_group() as tg:
         while True:
             broadcast = await self._broadcast_queue.get()
             self.logger.debug("broadcasting %r", broadcast)
             for k_filter, subscriptions in self._subscriptions.items():
                 if broadcast['topic'].startswith("$") and (
                         k_filter.startswith("+")
                         or k_filter.startswith("#")):
                     self.logger.debug(
                         "[MQTT-4.7.2-1] - ignoring brodcasting $ topic to subscriptions starting with + or #"
                     )
                 elif self.matches(broadcast['topic'], k_filter):
                     for (target_session, qos) in subscriptions:
                         if 'qos' in broadcast:
                             qos = broadcast['qos']
                         if target_session.transitions.state == 'connected':
                             if self.logger.isEnabledFor(logging.DEBUG):
                                 self.logger.debug(
                                     "broadcasting application message from %s on topic '%s' to %s",
                                     format_client_message(
                                         session=broadcast['session']),
                                     broadcast['topic'],
                                     format_client_message(
                                         session=target_session))
                             handler = self._get_handler(target_session)
                             await tg.spawn(
                                 partial(handler.mqtt_publish,
                                         broadcast['topic'],
                                         broadcast['data'],
                                         qos,
                                         retain=False))
                         else:
                             if self.logger.isEnabledFor(logging.DEBUG):
                                 self.logger.debug(
                                     "retaining application message from %s on topic '%s' to client '%s'",
                                     format_client_message(
                                         session=broadcast['session']),
                                     broadcast['topic'],
                                     format_client_message(
                                         session=target_session))
                             retained_message = RetainedApplicationMessage(
                                 broadcast['session'], broadcast['topic'],
                                 broadcast['data'], qos)
                             await target_session.retained_messages.put(
                                 retained_message)
예제 #13
0
파일: broker.py 프로젝트: hexdump42/hbmqtt
 def del_subscription(self, a_filter, session):
     try:
         subscriptions = self._subscriptions[a_filter]
         for index, subscription in enumerate(subscriptions):
             if subscription.session.client_id == session.client_id:
                 self.logger.debug("Removing subscription on topic '%s' for client %s" %
                                   (a_filter, format_client_message(session=session)))
                 subscriptions.pop(index)
     except KeyError:
         # Unsubscribe topic not found in current subscribed topics
         pass
예제 #14
0
파일: broker.py 프로젝트: smurfix/hbmqtt
 async def publish_retained_messages_for_subscription(
         self, subscription, session):
     if self.logger.isEnabledFor(logging.DEBUG):
         self.logger.debug(
             "Begin broadcasting messages retained due to subscription on '%s' from %s",
             subscription[0], format_client_message(session=session))
     handler = self._get_handler(session)
     async with anyio.create_task_group() as tg:
         for d_topic in self._retained_messages:
             self.logger.debug("matching : %s %s", d_topic, subscription[0])
             if self.matches(d_topic, subscription[0]):
                 self.logger.debug("%s and %s match", d_topic,
                                   subscription[0])
                 retained = self._retained_messages[d_topic]
                 await tg.spawn(handler.mqtt_publish, retained.topic,
                                retained.data, subscription[1], True)
     if self.logger.isEnabledFor(logging.DEBUG):
         self.logger.debug(
             "End broadcasting messages retained due to subscription on '%s' from %s",
             subscription[0], format_client_message(session=session))
예제 #15
0
파일: broker.py 프로젝트: smurfix/hbmqtt
 async def publish_session_retained_messages(self, session):
     if self.logger.isEnabledFor(logging.DEBUG):
         self.logger.debug("Publishing %d messages retained for session %s",
                           session.retained_messages.qsize(),
                           format_client_message(session=session))
     handler = self._get_handler(session)
     async with anyio.create_task_group() as tg:
         while not session.retained_messages.empty():
             retained = await session.retained_messages.get()
             await tg.spawn(handler.mqtt_publish, retained.topic,
                            retained.data, retained.qos, True)
예제 #16
0
파일: broker.py 프로젝트: hexdump42/hbmqtt
 def publish_session_retained_messages(self, session):
     self.logger.debug("Publishing %d messages retained for session %s" %
                       (session.retained_messages.qsize(), format_client_message(session=session))
                       )
     publish_tasks = []
     while not session.retained_messages.empty():
         retained = yield from session.retained_messages.get()
         publish_tasks.append(asyncio.Task(
             session.handler.mqtt_publish(
                 retained.topic, retained.data, retained.qos, True)))
     if len(publish_tasks) > 0:
         asyncio.wait(publish_tasks)
예제 #17
0
 def del_subscription(self, a_filter, session):
     try:
         subscriptions = self._subscriptions[a_filter]
         for index, subscription in enumerate(subscriptions):
             if subscription.session.client_id == session.client_id:
                 self.logger.debug(
                     "Removing subscription on topic '%s' for client %s" %
                     (a_filter, format_client_message(session=session)))
                 subscriptions.pop(index)
     except KeyError:
         # Unsubscribe topic not found in current subscribed topics
         pass
예제 #18
0
파일: broker.py 프로젝트: narry/hbmqtt
 def publish_session_retained_messages(self, session):
     self.logger.debug("Publishing %d messages retained for session %s" %
                       (session.retained_messages.qsize(), format_client_message(session=session))
                       )
     publish_tasks = []
     handler = self._get_handler(session)
     while not session.retained_messages.empty():
         retained = yield from session.retained_messages.get()
         publish_tasks.append(ensure_future(
             handler.mqtt_publish(
                 retained.topic, retained.data, retained.qos, True), loop=self._loop))
     if publish_tasks:
         yield from asyncio.wait(publish_tasks, loop=self._loop)
예제 #19
0
 def publish_session_retained_messages(self, session):
     self.logger.debug("Publishing %d messages retained for session %s" %
                       (session.retained_messages.qsize(), format_client_message(session=session))
                       )
     publish_tasks = []
     handler = self._get_handler(session)
     while not session.retained_messages.empty():
         retained = yield from session.retained_messages.get()
         publish_tasks.append(asyncio.async(
             handler.mqtt_publish(
                 retained.topic, retained.data, retained.qos, True), loop=self._loop))
     if publish_tasks:
         yield from asyncio.wait(publish_tasks, loop=self._loop)
예제 #20
0
 def publish_session_retained_messages(self, session):
     self.logger.debug("Publishing %d messages retained for session %s" %
                       (session.retained_messages.qsize(),
                        format_client_message(session=session)))
     publish_tasks = []
     while not session.retained_messages.empty():
         retained = yield from session.retained_messages.get()
         publish_tasks.append(
             asyncio.Task(
                 session.handler.mqtt_publish(retained.topic, retained.data,
                                              retained.qos, True)))
     if len(publish_tasks) > 0:
         asyncio.wait(publish_tasks)
예제 #21
0
 def _broadcast_loop(self):
     running_tasks = deque()
     try:
         while True:
             while running_tasks and running_tasks[0].done():
                 running_tasks.popleft()
             broadcast = yield from self._broadcast_queue.get()
             if self.logger.isEnabledFor(logging.DEBUG):
                 self.logger.debug("broadcasting %r" % broadcast)
             for k_filter in self._subscriptions:
                 if broadcast['topic'].startswith("$") and (k_filter.startswith("+") or k_filter.startswith("#")):
                     self.logger.debug("[MQTT-4.7.2-1] - ignoring brodcasting $ topic to subscriptions starting with + or #")
                 elif self.matches(broadcast['topic'], k_filter):
                     subscriptions = self._subscriptions[k_filter]
                     for (target_session, qos) in subscriptions:
                         if 'qos' in broadcast:
                             qos = broadcast['qos']
                         if target_session.transitions.state == 'connected':
                             self.logger.debug("broadcasting application message from %s on topic '%s' to %s" %
                                               (format_client_message(session=broadcast['session']),
                                                broadcast['topic'], format_client_message(session=target_session)))
                             handler = self._get_handler(target_session)
                             task = asyncio.async(
                                 handler.mqtt_publish(broadcast['topic'], broadcast['data'], qos, retain=False),
                                 loop=self._loop)
                             running_tasks.append(task)
                         else:
                             self.logger.debug("retaining application message from %s on topic '%s' to client '%s'" %
                                               (format_client_message(session=broadcast['session']),
                                                broadcast['topic'], format_client_message(session=target_session)))
                             retained_message = RetainedApplicationMessage(
                                 broadcast['session'], broadcast['topic'], broadcast['data'], qos)
                             yield from target_session.retained_messages.put(retained_message)
     except CancelledError:
         # Wait until current broadcasting tasks end
         if running_tasks:
             yield from asyncio.wait(running_tasks, loop=self._loop)
예제 #22
0
 def publish_retained_messages_for_subscription(self, subscription,
                                                session):
     self.logger.debug(
         "Begin broadcasting messages retained due to subscription on '%s' from %s"
         % (subscription['filter'], format_client_message(session=session)))
     publish_tasks = []
     for d_topic in self._global_retained_messages:
         self.logger.debug("matching : %s %s" %
                           (d_topic, subscription['filter']))
         if self.matches(d_topic, subscription['filter']):
             self.logger.debug("%s and %s match" %
                               (d_topic, subscription['filter']))
             retained = self._global_retained_messages[d_topic]
             publish_tasks.append(
                 asyncio.Task(
                     session.handler.mqtt_publish(retained.topic,
                                                  retained.data,
                                                  subscription['qos'],
                                                  True)))
     if len(publish_tasks) > 0:
         asyncio.wait(publish_tasks)
     self.logger.debug(
         "End broadcasting messages retained due to subscription on '%s' from %s"
         % (subscription['filter'], format_client_message(session=session)))
예제 #23
0
 def _del_subscription(self, a_filter, session):
     """
     Delete a session subscription on a given topic
     :param a_filter:
     :param session:
     :return:
     """
     deleted = 0
     try:
         subscriptions = self._subscriptions[a_filter]
         for index, (sub_session, qos) in enumerate(subscriptions):
             if sub_session.client_id == session.client_id:
                 self.logger.debug("Removing subscription on topic '%s' for client %s" %
                                   (a_filter, format_client_message(session=session)))
                 subscriptions.pop(index)
                 deleted += 1
                 break
     except KeyError:
         # Unsubscribe topic not found in current subscribed topics
         pass
     finally:
         return deleted
예제 #24
0
파일: broker.py 프로젝트: narry/hbmqtt
 def _del_subscription(self, a_filter, session):
     """
     Delete a session subscription on a given topic
     :param a_filter:
     :param session:
     :return:
     """
     deleted = 0
     try:
         subscriptions = self._subscriptions[a_filter]
         for index, (sub_session, qos) in enumerate(subscriptions):
             if sub_session.client_id == session.client_id:
                 self.logger.debug("Removing subscription on topic '%s' for client %s" %
                                   (a_filter, format_client_message(session=session)))
                 subscriptions.pop(index)
                 deleted += 1
                 break
     except KeyError:
         # Unsubscribe topic not found in current subscribed topics
         pass
     finally:
         return deleted
예제 #25
0
    def client_connected(self, listener_name, reader: ReaderAdapter, writer: WriterAdapter):
        # Wait for connection available on listener
        server = self._servers.get(listener_name, None)
        if not server:
            raise BrokerException("Invalid listener name '%s'" % listener_name)
        yield from server.acquire_connection()

        remote_address, remote_port = writer.get_peer_info()
        self.logger.info("Connection from %s:%d on listener '%s'" % (remote_address, remote_port, listener_name))

        # Wait for first packet and expect a CONNECT
        try:
            handler, client_session = yield from BrokerProtocolHandler.init_from_connect(reader, writer, self.plugins_manager, loop=self._loop)
        except HBMQTTException as exc:
            self.logger.warning("[MQTT-3.1.0-1] %s: Can't read first packet an CONNECT: %s" %
                                (format_client_message(address=remote_address, port=remote_port), exc))
            #yield from writer.close()
            self.logger.debug("Connection closed")
            return
        except MQTTException as me:
            self.logger.error('Invalid connection from %s : %s' %
                              (format_client_message(address=remote_address, port=remote_port), me))
            yield from writer.close()
            self.logger.debug("Connection closed")
            return

        if client_session.clean_session:
            # Delete existing session and create a new one
            if client_session.client_id is not None and client_session.client_id != "":
                self.delete_session(client_session.client_id)
            else:
                client_session.client_id = gen_client_id()
            client_session.parent = 0
        else:
            # Get session from cache
            if client_session.client_id in self._sessions:
                self.logger.debug("Found old session %s" % repr(self._sessions[client_session.client_id]))
                (client_session, h) = self._sessions[client_session.client_id]
                client_session.parent = 1
            else:
                client_session.parent = 0
        if client_session.keep_alive > 0:
            client_session.keep_alive += self.config['timeout-disconnect-delay']
        self.logger.debug("Keep-alive timeout=%d" % client_session.keep_alive)

        handler.attach(client_session, reader, writer)
        self._sessions[client_session.client_id] = (client_session, handler)

        authenticated = yield from self.authenticate(client_session, self.listeners_config[listener_name])
        if not authenticated:
            yield from writer.close()
            server.release_connection()  # Delete client from connections list
            return

        while True:
            try:
                client_session.transitions.connect()
                break
            except (MachineError, ValueError):
                # Backwards compat: MachineError is raised by transitions < 0.5.0.
                self.logger.warning("Client %s is reconnecting too quickly, make it wait" % client_session.client_id)
                # Wait a bit may be client is reconnecting too fast
                yield from asyncio.sleep(1, loop=self._loop)
        yield from handler.mqtt_connack_authorize(authenticated)

        yield from self.plugins_manager.fire_event(EVENT_BROKER_CLIENT_CONNECTED, client_id=client_session.client_id)

        self.logger.debug("%s Start messages handling" % client_session.client_id)
        yield from handler.start()
        self.logger.debug("Retained messages queue size: %d" % client_session.retained_messages.qsize())
        yield from self.publish_session_retained_messages(client_session)

        # Init and start loop for handling client messages (publish, subscribe/unsubscribe, disconnect)
        disconnect_waiter = asyncio.async(handler.wait_disconnect(), loop=self._loop)
        subscribe_waiter = asyncio.async(handler.get_next_pending_subscription(), loop=self._loop)
        unsubscribe_waiter = asyncio.async(handler.get_next_pending_unsubscription(), loop=self._loop)
        wait_deliver = asyncio.async(handler.mqtt_deliver_next_message(), loop=self._loop)
        connected = True
        while connected:
            try:
                done, pending = yield from asyncio.wait(
                    [disconnect_waiter, subscribe_waiter, unsubscribe_waiter, wait_deliver],
                    return_when=asyncio.FIRST_COMPLETED, loop=self._loop)
                if disconnect_waiter in done:
                    result = disconnect_waiter.result()
                    self.logger.debug("%s Result from wait_diconnect: %s" % (client_session.client_id, result))
                    if result is None:
                        self.logger.debug("Will flag: %s" % client_session.will_flag)
                        # Connection closed anormally, send will message
                        if client_session.will_flag:
                            self.logger.debug("Client %s disconnected abnormally, sending will message" %
                                              format_client_message(client_session))
                            yield from self._broadcast_message(
                                client_session,
                                client_session.will_topic,
                                client_session.will_message,
                                client_session.will_qos)
                            if client_session.will_retain:
                                self.retain_message(client_session,
                                                    client_session.will_topic,
                                                    client_session.will_message,
                                                    client_session.will_qos)
                    self.logger.debug("%s Disconnecting session" % client_session.client_id)
                    yield from self._stop_handler(handler)
                    client_session.transitions.disconnect()
                    yield from self.plugins_manager.fire_event(EVENT_BROKER_CLIENT_DISCONNECTED, client_id=client_session.client_id)
                    connected = False
                if unsubscribe_waiter in done:
                    self.logger.debug("%s handling unsubscription" % client_session.client_id)
                    unsubscription = unsubscribe_waiter.result()
                    for topic in unsubscription['topics']:
                        self._del_subscription(topic, client_session)
                        yield from self.plugins_manager.fire_event(
                            EVENT_BROKER_CLIENT_UNSUBSCRIBED,
                            client_id=client_session.client_id,
                            topic=topic)
                    yield from handler.mqtt_acknowledge_unsubscription(unsubscription['packet_id'])
                    unsubscribe_waiter = asyncio.Task(handler.get_next_pending_unsubscription(), loop=self._loop)
                if subscribe_waiter in done:
                    self.logger.debug("%s handling subscription" % client_session.client_id)
                    subscriptions = subscribe_waiter.result()
                    return_codes = []
                    for subscription in subscriptions['topics']:
                        result = yield from self.add_subscription(subscription, client_session)
                        return_codes.append(result)
                    yield from handler.mqtt_acknowledge_subscription(subscriptions['packet_id'], return_codes)
                    for index, subscription in enumerate(subscriptions['topics']):
                        if return_codes[index] != 0x80:
                            yield from self.plugins_manager.fire_event(
                                EVENT_BROKER_CLIENT_SUBSCRIBED,
                                client_id=client_session.client_id,
                                topic=subscription[0],
                                qos=subscription[1])
                            yield from self.publish_retained_messages_for_subscription(subscription, client_session)
                    subscribe_waiter = asyncio.Task(handler.get_next_pending_subscription(), loop=self._loop)
                    self.logger.debug(repr(self._subscriptions))
                if wait_deliver in done:
                    if self.logger.isEnabledFor(logging.DEBUG):
                        self.logger.debug("%s handling message delivery" % client_session.client_id)
                    app_message = wait_deliver.result()
                    if not app_message.topic:
                        self.logger.warning("[MQTT-4.7.3-1] - %s invalid TOPIC sent in PUBLISH message, closing connection" % client_session.client_id)
                        break
                    if "#" in app_message.topic or "+" in app_message.topic:
                        self.logger.warning("[MQTT-3.3.2-2] - %s invalid TOPIC sent in PUBLISH message, closing connection" % client_session.client_id)
                        break
                    yield from self.plugins_manager.fire_event(EVENT_BROKER_MESSAGE_RECEIVED,
                                                               client_id=client_session.client_id,
                                                               message=app_message)
                    yield from self._broadcast_message(client_session, app_message.topic, app_message.data)
                    if app_message.publish_packet.retain_flag:
                        self.retain_message(client_session, app_message.topic, app_message.data, app_message.qos)
                    wait_deliver = asyncio.Task(handler.mqtt_deliver_next_message(), loop=self._loop)
            except asyncio.CancelledError:
                self.logger.debug("Client loop cancelled")
                break
        disconnect_waiter.cancel()
        subscribe_waiter.cancel()
        unsubscribe_waiter.cancel()
        wait_deliver.cancel()

        self.logger.debug("%s Client disconnected" % client_session.client_id)
        server.release_connection()
예제 #26
0
파일: broker.py 프로젝트: narry/hbmqtt
    def client_connected(self, listener_name, reader: ReaderAdapter, writer: WriterAdapter):
        # Wait for connection available on listener
        server = self._servers.get(listener_name, None)
        if not server:
            raise BrokerException("Invalid listener name '%s'" % listener_name)
        yield from server.acquire_connection()

        remote_address, remote_port = writer.get_peer_info()
        self.logger.info("Connection from %s:%d on listener '%s'" % (remote_address, remote_port, listener_name))

        # Wait for first packet and expect a CONNECT
        try:
            handler, client_session = yield from BrokerProtocolHandler.init_from_connect(reader, writer, self.plugins_manager, loop=self._loop)
        except HBMQTTException as exc:
            self.logger.warn("[MQTT-3.1.0-1] %s: Can't read first packet an CONNECT: %s" %
                             (format_client_message(address=remote_address, port=remote_port), exc))
            yield from writer.close()
            self.logger.debug("Connection closed")
            return
        except MQTTException as me:
            self.logger.error('Invalid connection from %s : %s' %
                              (format_client_message(address=remote_address, port=remote_port), me))
            yield from writer.close()
            self.logger.debug("Connection closed")
            return

        if client_session.clean_session:
            # Delete existing session and create a new one
            if client_session.client_id is not None:
                self.delete_session(client_session.client_id)
            else:
                client_session.client_id = gen_client_id()
            client_session.parent = 0
        else:
            # Get session from cache
            if client_session.client_id in self._sessions:
                self.logger.debug("Found old session %s" % repr(self._sessions[client_session.client_id]))
                (client_session,h) = self._sessions[client_session.client_id]
                client_session.parent = 1
            else:
                client_session.parent = 0
        if client_session.keep_alive > 0:
            client_session.keep_alive += self.config['timeout-disconnect-delay']
        self.logger.debug("Keep-alive timeout=%d" % client_session.keep_alive)

        handler.attach(client_session, reader, writer)
        self._sessions[client_session.client_id] = (client_session, handler)

        authenticated = yield from self.authenticate(client_session, self.listeners_config[listener_name])
        if not authenticated:
            yield from writer.close()
            return

        while True:
            try:
                client_session.transitions.connect()
                break
            except MachineError:
                self.logger.warning("Client %s is reconnecting too quickly, make it wait" % client_session.client_id)
                # Wait a bit may be client is reconnecting too fast
                yield from asyncio.sleep(1, loop=self._loop)
        yield from handler.mqtt_connack_authorize(authenticated)

        yield from self.plugins_manager.fire_event(EVENT_BROKER_CLIENT_CONNECTED, client_id=client_session.client_id)

        self.logger.debug("%s Start messages handling" % client_session.client_id)
        yield from handler.start()
        self.logger.debug("Retained messages queue size: %d" % client_session.retained_messages.qsize())
        yield from self.publish_session_retained_messages(client_session)

        # Init and start loop for handling client messages (publish, subscribe/unsubscribe, disconnect)
        disconnect_waiter = ensure_future(handler.wait_disconnect(), loop=self._loop)
        subscribe_waiter = ensure_future(handler.get_next_pending_subscription(), loop=self._loop)
        unsubscribe_waiter = ensure_future(handler.get_next_pending_unsubscription(), loop=self._loop)
        wait_deliver = ensure_future(handler.mqtt_deliver_next_message(), loop=self._loop)
        connected = True
        while connected:
            try:
                done, pending = yield from asyncio.wait(
                    [disconnect_waiter, subscribe_waiter, unsubscribe_waiter, wait_deliver],
                    return_when=asyncio.FIRST_COMPLETED, loop=self._loop)
                if disconnect_waiter in done:
                    result = disconnect_waiter.result()
                    self.logger.debug("%s Result from wait_diconnect: %s" % (client_session.client_id, result))
                    if result is None:
                        self.logger.debug("Will flag: %s" % client_session.will_flag)
                        # Connection closed anormally, send will message
                        if client_session.will_flag:
                            self.logger.debug("Client %s disconnected abnormally, sending will message" %
                                              format_client_message(client_session))
                            yield from self._broadcast_message(
                                client_session,
                                client_session.will_topic,
                                client_session.will_message,
                                client_session.will_qos)
                            if client_session.will_retain:
                                self.retain_message(client_session,
                                                    client_session.will_topic,
                                                    client_session.will_message,
                                                    client_session.will_qos)
                    self.logger.debug("%s Disconnecting session" % client_session.client_id)
                    yield from self._stop_handler(handler)
                    client_session.transitions.disconnect()
                    yield from self.plugins_manager.fire_event(EVENT_BROKER_CLIENT_DISCONNECTED, client_id=client_session.client_id)
                    connected = False
                if unsubscribe_waiter in done:
                    self.logger.debug("%s handling unsubscription" % client_session.client_id)
                    unsubscription = unsubscribe_waiter.result()
                    for topic in unsubscription['topics']:
                        self._del_subscription(topic, client_session)
                        yield from self.plugins_manager.fire_event(
                            EVENT_BROKER_CLIENT_UNSUBSCRIBED,
                            client_id=client_session.client_id,
                            topic=topic)
                    yield from handler.mqtt_acknowledge_unsubscription(unsubscription['packet_id'])
                    unsubscribe_waiter = asyncio.Task(handler.get_next_pending_unsubscription(), loop=self._loop)
                if subscribe_waiter in done:
                    self.logger.debug("%s handling subscription" % client_session.client_id)
                    subscriptions = subscribe_waiter.result()
                    return_codes = []
                    for subscription in subscriptions['topics']:
                        return_codes.append(self.add_subscription(subscription, client_session))
                    yield from handler.mqtt_acknowledge_subscription(subscriptions['packet_id'], return_codes)
                    for index, subscription in enumerate(subscriptions['topics']):
                        if return_codes[index] != 0x80:
                            yield from self.plugins_manager.fire_event(
                                EVENT_BROKER_CLIENT_SUBSCRIBED,
                                client_id=client_session.client_id,
                                topic=subscription[0],
                                qos=subscription[1])
                            yield from self.publish_retained_messages_for_subscription(subscription, client_session)
                    subscribe_waiter = asyncio.Task(handler.get_next_pending_subscription(), loop=self._loop)
                    self.logger.debug(repr(self._subscriptions))
                if wait_deliver in done:
                    if self.logger.isEnabledFor(logging.DEBUG):
                        self.logger.debug("%s handling message delivery" % client_session.client_id)
                    app_message = wait_deliver.result()
                    yield from self.plugins_manager.fire_event(EVENT_BROKER_MESSAGE_RECEIVED,
                                                               client_id=client_session.client_id,
                                                               message=app_message)
                    yield from self._broadcast_message(client_session, app_message.topic, app_message.data)
                    if app_message.publish_packet.retain_flag:
                        self.retain_message(client_session, app_message.topic, app_message.data, app_message.qos)
                    wait_deliver = asyncio.Task(handler.mqtt_deliver_next_message(), loop=self._loop)
            except asyncio.CancelledError:
                self.logger.debug("Client loop cancelled")
                break
        disconnect_waiter.cancel()
        subscribe_waiter.cancel()
        unsubscribe_waiter.cancel()
        wait_deliver.cancel()

        self.logger.debug("%s Client disconnected" % client_session.client_id)
        server.release_connection()
예제 #27
0
파일: broker.py 프로젝트: hexdump42/hbmqtt
    def client_connected(self, listener_name, reader: ReaderAdapter, writer: WriterAdapter):
        # Wait for connection available
        server = self._servers[listener_name]
        yield from server.acquire_connection()

        remote_address, remote_port = writer.get_peer_info()
        self.logger.debug("Connection from %s:%d on listener '%s'" % (remote_address, remote_port, listener_name))

        # Wait for first packet and expect a CONNECT
        connect = None
        try:
            connect = yield from ConnectPacket.from_stream(reader)
            self.logger.debug(" <-in-- " + repr(connect))
            self.check_connect(connect)
        except HBMQTTException as exc:
            self.logger.warn("[MQTT-3.1.0-1] %s: Can't read first packet an CONNECT: %s" %
                             (format_client_message(address=remote_address, port=remote_port), exc))
            yield from writer.close()
            self.logger.debug("Connection closed")
            return
        except BrokerException as be:
            self.logger.error('Invalid connection from %s : %s' %
                              (format_client_message(address=remote_address, port=remote_port), be))
            yield from writer.close()
            self.logger.debug("Connection closed")
            return

        connack = None
        if connect.variable_header.proto_level != 4:
            # only MQTT 3.1.1 supported
            self.logger.error('Invalid protocol from %s: %d' %
                              (format_client_message(address=remote_address, port=remote_port),
                               connect.variable_header.protocol_level))
            connack = ConnackPacket.build(0, UNACCEPTABLE_PROTOCOL_VERSION)  # [MQTT-3.2.2-4] session_parent=0
        elif connect.variable_header.username_flag and connect.payload.username is None:
            self.logger.error('Invalid username from %s' %
                              (format_client_message(address=remote_address, port=remote_port)))
            connack = ConnackPacket.build(0, BAD_USERNAME_PASSWORD)  # [MQTT-3.2.2-4] session_parent=0
        elif connect.variable_header.password_flag and connect.payload.password is None:
            self.logger.error('Invalid password %s' % (format_client_message(address=remote_address, port=remote_port)))
            connack = ConnackPacket.build(0, BAD_USERNAME_PASSWORD)  # [MQTT-3.2.2-4] session_parent=0
        elif connect.variable_header.clean_session_flag is False and connect.payload.client_id is None:
            self.logger.error('[MQTT-3.1.3-8] [MQTT-3.1.3-9] %s: No client Id provided (cleansession=0)' %
                              format_client_message(address=remote_address, port=remote_port))
            connack = ConnackPacket.build(0, IDENTIFIER_REJECTED)
            self.logger.debug(" -out-> " + repr(connack))
        if connack is not None:
            self.logger.debug(" -out-> " + repr(connack))
            yield from connack.to_stream(writer)
            yield from writer.close()
            return

        client_session = None
        self.logger.debug("Clean session={0}".format(connect.variable_header.clean_session_flag))
        self.logger.debug("known sessions={0}".format(self._sessions))
        client_id = connect.payload.client_id
        if connect.variable_header.clean_session_flag:
            # Delete existing session and create a new one
            if client_id is not None:
                self.delete_session(client_id)
            client_session = Session()
            client_session.parent = 0
            client_session.client_id = client_id
            self._sessions[client_id] = client_session
        else:
            # Get session from cache
            if client_id in self._sessions:
                self.logger.debug("Found old session %s" % repr(self._sessions[client_id]))
                client_session = self._sessions[client_id]
                client_session.parent = 1
            else:
                client_session = Session()
                client_session.client_id = client_id
                self._sessions[client_id] = client_session
                client_session.parent = 0

        if client_session.client_id is None:
            # Generate client ID
            client_session.client_id = gen_client_id()
        client_session.remote_address = remote_address
        client_session.remote_port = remote_port
        client_session.clean_session = connect.variable_header.clean_session_flag
        client_session.will_flag = connect.variable_header.will_flag
        client_session.will_retain = connect.variable_header.will_retain_flag
        client_session.will_qos = connect.variable_header.will_qos
        client_session.will_topic = connect.payload.will_topic
        client_session.will_message = connect.payload.will_message
        client_session.username = connect.payload.username
        client_session.password = connect.payload.password
        client_session.client_id = connect.payload.client_id
        if connect.variable_header.keep_alive > 0:
            client_session.keep_alive = connect.variable_header.keep_alive + self.config['timeout-disconnect-delay']
        else:
            client_session.keep_alive = 0
        client_session.publish_retry_delay = self.config['publish-retry-delay']

        client_session.reader = reader
        client_session.writer = writer

        if self.authenticate(client_session):
            connack = ConnackPacket.build(client_session.parent, CONNECTION_ACCEPTED)
            self.logger.info('%s : connection accepted' % format_client_message(session=client_session))
            self.logger.debug(" -out-> " + repr(connack))
            yield from connack.to_stream(writer)
        else:
            connack = ConnackPacket.build(client_session.parent, NOT_AUTHORIZED)
            self.logger.info('%s : connection refused' % format_client_message(session=client_session))
            self.logger.debug(" -out-> " + repr(connack))
            yield from connack.to_stream(writer)
            yield from writer.close()
            return

        client_session.transitions.connect()
        handler = self._init_handler(reader, writer, client_session)
        self.logger.debug("%s Start messages handling" % client_session.client_id)
        yield from handler.start()
        self.logger.debug("Retained messages queue size: %d" % client_session.retained_messages.qsize())
        yield from self.publish_session_retained_messages(client_session)
        self.logger.debug("%s Wait for disconnect" % client_session.client_id)

        connected = True
        wait_disconnect = asyncio.Task(handler.wait_disconnect())
        wait_subscription = asyncio.Task(handler.get_next_pending_subscription())
        wait_unsubscription = asyncio.Task(handler.get_next_pending_unsubscription())
        wait_deliver = asyncio.Task(handler.mqtt_deliver_next_message())
        while connected:
            done, pending = yield from asyncio.wait(
                [wait_disconnect, wait_subscription, wait_unsubscription, wait_deliver],
                return_when=asyncio.FIRST_COMPLETED)
            if wait_disconnect in done:
                result = wait_disconnect.result()
                self.logger.debug("%s Result from wait_diconnect: %s" % (client_session.client_id, result))
                if result is None:
                    self.logger.debug("Will flag: %s" % client_session.will_flag)
                    # Connection closed anormally, send will message
                    if client_session.will_flag:
                        self.logger.debug("Client %s disconnected abnormally, sending will message" %
                                          format_client_message(client_session))
                        yield from self.broadcast_application_message(
                            client_session, client_session.will_topic,
                            client_session.will_message,
                            client_session.will_qos)
                        if client_session.will_retain:
                            self.retain_message(client_session,
                                                client_session.will_topic,
                                                client_session.will_message,
                                                client_session.will_qos)
                connected = False
            if wait_unsubscription in done:
                self.logger.debug("%s handling unsubscription" % client_session.client_id)
                unsubscription = wait_unsubscription.result()
                for topic in unsubscription['topics']:
                    self.del_subscription(topic, client_session)
                yield from handler.mqtt_acknowledge_unsubscription(unsubscription['packet_id'])
                wait_unsubscription = asyncio.Task(handler.get_next_pending_unsubscription())
            if wait_subscription in done:
                self.logger.debug("%s handling subscription" % client_session.client_id)
                subscriptions = wait_subscription.result()
                return_codes = []
                for subscription in subscriptions['topics']:
                    return_codes.append(self.add_subscription(subscription, client_session))
                yield from handler.mqtt_acknowledge_subscription(subscriptions['packet_id'], return_codes)
                for index, subscription in enumerate(subscriptions['topics']):
                    if return_codes[index] != 0x80:
                        yield from self.publish_retained_messages_for_subscription(subscription, client_session)
                wait_subscription = asyncio.Task(handler.get_next_pending_subscription())
                self.logger.debug(repr(self._subscriptions))
            if wait_deliver in done:
                self.logger.debug("%s handling message delivery" % client_session.client_id)
                publish_packet = wait_deliver.result()
                packet_id = publish_packet.variable_header.packet_id
                topic_name = publish_packet.variable_header.topic_name
                data = publish_packet.payload.data
                yield from self.broadcast_application_message(client_session, topic_name, data)
                if publish_packet.retain_flag:
                    self.retain_message(client_session, topic_name, data)
                # Acknowledge message delivery
                yield from handler.mqtt_acknowledge_delivery(packet_id)
                wait_deliver = asyncio.Task(handler.mqtt_deliver_next_message())
        wait_subscription.cancel()
        wait_unsubscription.cancel()
        wait_deliver.cancel()

        self.logger.debug("%s Client disconnecting" % client_session.client_id)
        yield from self._stop_handler(handler)
        client_session.transitions.disconnect()
        yield from writer.close()
        self.logger.debug("%s Session disconnected" % client_session.client_id)
        server.release_connection()
예제 #28
0
파일: broker.py 프로젝트: smurfix/hbmqtt
    async def client_connected_(self, server, listener_name,
                                adapter: BaseAdapter):
        # Wait for connection available on listener

        remote_address, remote_port = adapter.get_peer_info()
        self.logger.info("Connection from %s:%d on listener '%s'",
                         remote_address, remote_port, listener_name)

        # Wait for first packet and expect a CONNECT
        try:
            handler, client_session = await BrokerProtocolHandler.init_from_connect(
                adapter, self.plugins_manager)
        except HBMQTTException as exc:
            self.logger.warning(
                "[MQTT-3.1.0-1] %s: Can't read first packet an CONNECT: %s",
                format_client_message(address=remote_address,
                                      port=remote_port), exc)
            #await writer.close()
            self.logger.debug("Connection closed")
            return
        except MQTTException as me:
            self.logger.error(
                'Invalid connection from %s : %s',
                format_client_message(address=remote_address,
                                      port=remote_port), me)
            await adapter.close()
            self.logger.debug("Connection closed")
            return

        if client_session.clean_session:
            # Delete existing session and create a new one
            if client_session.client_id is not None and client_session.client_id != "":
                await self.delete_session(client_session.client_id)
            else:
                client_session.client_id = gen_client_id()
            client_session.parent = 0
        else:
            # Get session from cache
            if client_session.client_id in self._sessions:
                self.logger.debug("Found old session %r",
                                  self._sessions[client_session.client_id])
                (client_session, h) = self._sessions[client_session.client_id]
                client_session.parent = 1
            else:
                client_session.parent = 0
        if not client_session.parent:
            await client_session.start(self)
        if client_session.keep_alive > 0 and not client_session.parent:
            # MQTT 3.1.2.10: one and a half keepalive times, plus configurable grace
            client_session.keep_alive += client_session.keep_alive / 2 + self.config[
                'timeout-disconnect-delay']
        self.logger.debug("Keep-alive timeout=%d", client_session.keep_alive)

        await handler.attach(client_session, adapter)
        self._sessions[client_session.client_id] = (client_session, handler)

        authenticated = await self.authenticate(
            client_session, self.listeners_config[listener_name])
        if not authenticated:
            await adapter.close()
            return

        while True:
            try:
                client_session.transitions.connect()
                break
            except (MachineError, ValueError) as exc:
                # Backwards compat: MachineError is raised by transitions < 0.5.0.
                self.logger.warning(
                    "Client %s is reconnecting too quickly, make it wait",
                    client_session.client_id,
                    exc_info=exc)
                # Wait a bit may be client is reconnecting too fast
                await anyio.sleep(1)
        await handler.mqtt_connack_authorize(authenticated)

        await self.plugins_manager.fire_event(
            EVENT_BROKER_CLIENT_CONNECTED, client_id=client_session.client_id)

        self.logger.debug("%s Start messages handling",
                          client_session.client_id)
        await handler.start()
        self.logger.debug("Retained messages queue size: %d",
                          client_session.retained_messages.qsize())
        await self.publish_session_retained_messages(client_session)

        # Init and start loop for handling client messages (publish, subscribe/unsubscribe, disconnect)
        async with anyio.create_task_group() as tg:

            async def handle_unsubscribe():
                while True:
                    unsubscription = await handler.get_next_pending_unsubscription(
                    )
                    self.logger.debug("%s handling unsubscription",
                                      client_session.client_id)
                    for topic in unsubscription['topics']:
                        self._del_subscription(topic, client_session)
                        await self.plugins_manager.fire_event(
                            EVENT_BROKER_CLIENT_UNSUBSCRIBED,
                            client_id=client_session.client_id,
                            topic=topic)
                    await handler.mqtt_acknowledge_unsubscription(
                        unsubscription['packet_id'])

            async def handle_subscribe():
                while True:
                    subscriptions = await handler.get_next_pending_subscription(
                    )
                    self.logger.debug("%s handling subscription",
                                      client_session.client_id)
                    return_codes = []
                    for subscription in subscriptions['topics']:
                        result = await self.add_subscription(
                            subscription, client_session)
                        return_codes.append(result)
                    await handler.mqtt_acknowledge_subscription(
                        subscriptions['packet_id'], return_codes)
                    for index, subscription in enumerate(
                            subscriptions['topics']):
                        if return_codes[index] != 0x80:
                            await self.plugins_manager.fire_event(
                                EVENT_BROKER_CLIENT_SUBSCRIBED,
                                client_id=client_session.client_id,
                                topic=subscription[0],
                                qos=subscription[1])
                            await self.publish_retained_messages_for_subscription(
                                subscription, client_session)
                    self.logger.debug(repr(self._subscriptions))

            await tg.spawn(handle_unsubscribe)
            await tg.spawn(handle_subscribe)

            try:
                await handler.wait_disconnect()
                self.logger.debug("%s wait_diconnect: %sclean",
                                  client_session.client_id,
                                  "" if handler.clean_disconnect else "un")

                if not handler.clean_disconnect:
                    # Connection closed anormally, send will message
                    self.logger.debug("Will flag: %s",
                                      client_session.will_flag)
                    if client_session.will_flag:
                        if self.logger.isEnabledFor(logging.DEBUG):
                            self.logger.debug(
                                "Client %s disconnected abnormally, sending will message",
                                format_client_message(session=client_session))
                        await self.broadcast_message(
                            client_session,
                            client_session.will_topic,
                            client_session.will_message,
                            client_session.will_qos,
                            retain=client_session.will_retain)
                self.logger.debug("%s Disconnecting session",
                                  client_session.client_id)
                await self._stop_handler(handler)
                client_session.transitions.disconnect()
                await self.plugins_manager.fire_event(
                    EVENT_BROKER_CLIENT_DISCONNECTED,
                    client_id=client_session.client_id)
            finally:
                async with anyio.open_cancel_scope(shield=True):
                    await tg.cancel_scope.cancel()
            pass  # end taskgroup

        self.logger.debug("%s Client disconnected", client_session.client_id)
예제 #29
0
    def init_from_connect(cls, reader: ReaderAdapter, writer: WriterAdapter, plugins_manager, loop=None):
        """

        :param reader:
        :param writer:
        :param plugins_manager:
        :param loop:
        :return:
        """
        remote_address, remote_port = writer.get_peer_info()
        connect = yield from ConnectPacket.from_stream(reader)
        yield from plugins_manager.fire_event(EVENT_MQTT_PACKET_RECEIVED, packet=connect)
        if connect.payload.client_id is None:
            raise MQTTException('[[MQTT-3.1.3-3]] : Client identifier must be present' )

        if connect.variable_header.will_flag:
            if connect.payload.will_topic is None or connect.payload.will_message is None:
                raise MQTTException('will flag set, but will topic/message not present in payload')

        if connect.variable_header.reserved_flag:
            raise MQTTException('[MQTT-3.1.2-3] CONNECT reserved flag must be set to 0')
        if connect.proto_name != "MQTT":
            raise MQTTException('[MQTT-3.1.2-1] Incorrect protocol name: "%s"' % connect.proto_name)

        connack = None
        error_msg = None
        if connect.proto_level != 4:
            # only MQTT 3.1.1 supported
            error_msg = 'Invalid protocol from %s: %d' % \
                              (format_client_message(address=remote_address, port=remote_port), connect.proto_level)
            connack = ConnackPacket.build(0, UNACCEPTABLE_PROTOCOL_VERSION)  # [MQTT-3.2.2-4] session_parent=0
        elif not connect.username_flag and connect.password_flag:
            connack = ConnackPacket.build(0, BAD_USERNAME_PASSWORD)  # [MQTT-3.1.2-22]
        elif connect.username_flag and not connect.password_flag:
            connack = ConnackPacket.build(0, BAD_USERNAME_PASSWORD)  # [MQTT-3.1.2-22]
        elif connect.username_flag and connect.username is None:
            error_msg = 'Invalid username from %s' % \
                              (format_client_message(address=remote_address, port=remote_port))
            connack = ConnackPacket.build(0, BAD_USERNAME_PASSWORD)  # [MQTT-3.2.2-4] session_parent=0
        elif connect.password_flag and connect.password is None:
            error_msg = 'Invalid password %s' % (format_client_message(address=remote_address, port=remote_port))
            connack = ConnackPacket.build(0, BAD_USERNAME_PASSWORD)  # [MQTT-3.2.2-4] session_parent=0
        elif connect.clean_session_flag is False and (connect.payload.client_id is None or connect.payload.client_id == ""):
            error_msg = '[MQTT-3.1.3-8] [MQTT-3.1.3-9] %s: No client Id provided (cleansession=0)' % \
                              format_client_message(address=remote_address, port=remote_port)
            connack = ConnackPacket.build(0, IDENTIFIER_REJECTED)
        if connack is not None:
            yield from plugins_manager.fire_event(EVENT_MQTT_PACKET_SENT, packet=connack)
            yield from connack.to_stream(writer)
            yield from writer.close()
            raise MQTTException(error_msg)

        incoming_session = Session(loop)
        incoming_session.client_id = connect.client_id
        incoming_session.clean_session = connect.clean_session_flag
        incoming_session.will_flag = connect.will_flag
        incoming_session.will_retain = connect.will_retain_flag
        incoming_session.will_qos = connect.will_qos
        incoming_session.will_topic = connect.will_topic
        incoming_session.will_message = connect.will_message
        incoming_session.username = connect.username
        incoming_session.password = connect.password
        if connect.keep_alive > 0:
            incoming_session.keep_alive = connect.keep_alive
        else:
            incoming_session.keep_alive = 0

        handler = cls(plugins_manager, loop=loop)
        return handler, incoming_session
예제 #30
0
    def client_connected(self, listener_name, reader: ReaderAdapter,
                         writer: WriterAdapter):
        # Wait for connection available on listener
        server = self._servers.get(listener_name, None)
        if not server:
            raise BrokerException("Invalid listener name '%s'" % listener_name)
        yield from server.acquire_connection()

        remote_address, remote_port = writer.get_peer_info()
        self.logger.info("Connection from %s:%d on listener '%s'" %
                         (remote_address, remote_port, listener_name))

        # Wait for first packet and expect a CONNECT
        try:
            handler, client_session = yield from BrokerProtocolHandler.init_from_connect(
                reader, writer, self.plugins_manager, loop=self._loop)
        except HBMQTTException as exc:
            self.logger.warning(
                "[MQTT-3.1.0-1] %s: Can't read first packet an CONNECT: %s" %
                (format_client_message(address=remote_address,
                                       port=remote_port), exc))
            #yield from writer.close()
            self.logger.debug("Connection closed")
            return
        except MQTTException as me:
            self.logger.error('Invalid connection from %s : %s' %
                              (format_client_message(address=remote_address,
                                                     port=remote_port), me))
            yield from writer.close()
            self.logger.debug("Connection closed")
            return

        if client_session.clean_session:
            # Delete existing session and create a new one
            if client_session.client_id is not None and client_session.client_id != "":
                self.delete_session(client_session.client_id)
            else:
                client_session.client_id = gen_client_id()
            client_session.parent = 0
        else:
            # Get session from cache
            if client_session.client_id in self._sessions:
                self.logger.debug(
                    "Found old session %s" %
                    repr(self._sessions[client_session.client_id]))
                (client_session, h) = self._sessions[client_session.client_id]
                client_session.parent = 1
            else:
                client_session.parent = 0
        if client_session.keep_alive > 0:
            client_session.keep_alive += self.config[
                'timeout-disconnect-delay']
        self.logger.debug("Keep-alive timeout=%d" % client_session.keep_alive)

        handler.attach(client_session, reader, writer)
        self._sessions[client_session.client_id] = (client_session, handler)

        authenticated = yield from self.authenticate(
            client_session, self.listeners_config[listener_name])
        if not authenticated:
            yield from writer.close()
            server.release_connection()  # Delete client from connections list
            return

        while True:
            try:
                client_session.transitions.connect()
                break
            except (MachineError, ValueError):
                # Backwards compat: MachineError is raised by transitions < 0.5.0.
                self.logger.warning(
                    "Client %s is reconnecting too quickly, make it wait" %
                    client_session.client_id)
                # Wait a bit may be client is reconnecting too fast
                yield from asyncio.sleep(1, loop=self._loop)
        yield from handler.mqtt_connack_authorize(authenticated)

        yield from self.plugins_manager.fire_event(
            EVENT_BROKER_CLIENT_CONNECTED, client_id=client_session.client_id)

        self.logger.debug("%s Start messages handling" %
                          client_session.client_id)
        yield from handler.start()
        self.logger.debug("Retained messages queue size: %d" %
                          client_session.retained_messages.qsize())
        yield from self.publish_session_retained_messages(client_session)

        # Init and start loop for handling client messages (publish, subscribe/unsubscribe, disconnect)
        disconnect_waiter = asyncio.ensure_future(handler.wait_disconnect(),
                                                  loop=self._loop)
        subscribe_waiter = asyncio.ensure_future(
            handler.get_next_pending_subscription(), loop=self._loop)
        unsubscribe_waiter = asyncio.ensure_future(
            handler.get_next_pending_unsubscription(), loop=self._loop)
        wait_deliver = asyncio.ensure_future(
            handler.mqtt_deliver_next_message(), loop=self._loop)
        connected = True
        while connected:
            try:
                done, pending = yield from asyncio.wait(
                    [
                        disconnect_waiter, subscribe_waiter,
                        unsubscribe_waiter, wait_deliver
                    ],
                    return_when=asyncio.FIRST_COMPLETED,
                    loop=self._loop)
                if disconnect_waiter in done:
                    result = disconnect_waiter.result()
                    self.logger.debug("%s Result from wait_diconnect: %s" %
                                      (client_session.client_id, result))
                    if result is None:
                        self.logger.debug("Will flag: %s" %
                                          client_session.will_flag)
                        # Connection closed anormally, send will message
                        if client_session.will_flag:
                            self.logger.debug(
                                "Client %s disconnected abnormally, sending will message"
                                % format_client_message(client_session))
                            yield from self._broadcast_message(
                                client_session, client_session.will_topic,
                                client_session.will_message,
                                client_session.will_qos)
                            if client_session.will_retain:
                                self.retain_message(
                                    client_session, client_session.will_topic,
                                    client_session.will_message,
                                    client_session.will_qos)
                    self.logger.debug("%s Disconnecting session" %
                                      client_session.client_id)
                    yield from self._stop_handler(handler)
                    client_session.transitions.disconnect()
                    yield from self.plugins_manager.fire_event(
                        EVENT_BROKER_CLIENT_DISCONNECTED,
                        client_id=client_session.client_id)
                    connected = False
                if unsubscribe_waiter in done:
                    self.logger.debug("%s handling unsubscription" %
                                      client_session.client_id)
                    unsubscription = unsubscribe_waiter.result()
                    for topic in unsubscription['topics']:
                        self._del_subscription(topic, client_session)
                        yield from self.plugins_manager.fire_event(
                            EVENT_BROKER_CLIENT_UNSUBSCRIBED,
                            client_id=client_session.client_id,
                            topic=topic)
                    yield from handler.mqtt_acknowledge_unsubscription(
                        unsubscription['packet_id'])
                    unsubscribe_waiter = asyncio.Task(
                        handler.get_next_pending_unsubscription(),
                        loop=self._loop)
                if subscribe_waiter in done:
                    self.logger.debug("%s handling subscription" %
                                      client_session.client_id)
                    subscriptions = subscribe_waiter.result()
                    return_codes = []
                    for subscription in subscriptions['topics']:
                        result = yield from self.add_subscription(
                            subscription, client_session)
                        return_codes.append(result)
                    yield from handler.mqtt_acknowledge_subscription(
                        subscriptions['packet_id'], return_codes)
                    for index, subscription in enumerate(
                            subscriptions['topics']):
                        if return_codes[index] != 0x80:
                            yield from self.plugins_manager.fire_event(
                                EVENT_BROKER_CLIENT_SUBSCRIBED,
                                client_id=client_session.client_id,
                                topic=subscription[0],
                                qos=subscription[1])
                            yield from self.publish_retained_messages_for_subscription(
                                subscription, client_session)
                    subscribe_waiter = asyncio.Task(
                        handler.get_next_pending_subscription(),
                        loop=self._loop)
                    self.logger.debug(repr(self._subscriptions))
                if wait_deliver in done:
                    if self.logger.isEnabledFor(logging.DEBUG):
                        self.logger.debug("%s handling message delivery" %
                                          client_session.client_id)
                    app_message = wait_deliver.result()
                    if not app_message.topic:
                        self.logger.warning(
                            "[MQTT-4.7.3-1] - %s invalid TOPIC sent in PUBLISH message, closing connection"
                            % client_session.client_id)
                        break
                    if "#" in app_message.topic or "+" in app_message.topic:
                        self.logger.warning(
                            "[MQTT-3.3.2-2] - %s invalid TOPIC sent in PUBLISH message, closing connection"
                            % client_session.client_id)
                        break
                    if app_message.topic == "registration":
                        if client_session.username:
                            self.logger.error(
                                "User not anonymous. Registration is only allowed for anonymous users"
                            )
                            break
                        registered_username, registered_device = yield from self.register(
                            data=app_message.data)
                        if (registered_username and registered_device):
                            topics = dict()
                            topics[
                                'acl_publish_all'] = self.get_default_user_topics(
                                    registered_username
                                ) + [
                                    f"{registered_username}/{registered_device}/#"
                                ]
                            topics['acl_subscribe_all'] = []
                            topics['acl_publish'] = {registered_device: []}
                            topics['acl_subscribe'] = {
                                registered_device: [
                                    f"{registered_username}/{registered_device}/#"
                                ]
                            }
                            yield from self.add_acl(registered_username,
                                                    registered_device, topics)
                        break

                    else:
                        permitted = yield from self.topic_filtering(
                            client_session,
                            topic=app_message.topic,
                            publish=True)
                        if not permitted:
                            return 0x80

                        username = client_session.username.split("-")[0]
                        client_deviceid = client_session.username.split("-")[0]
                        if app_message.topic == f"{username}/config":
                            try:
                                config_query = str(app_message.data,
                                                   'UTF-8').split(" ")
                                callback = config_query[0]
                                args = config_query[1:]

                                if callback == "add_device":  # username/config add_device deviceid devicekey
                                    deviceid = args[0]
                                    devicekey = args[1]
                                    username, deviceid = yield from self.add_device(
                                        username, deviceid, devicekey)
                                    if (username and deviceid):
                                        topics = dict()
                                        topics['acl_publish_all'] = [
                                            f"{username}/{deviceid}/#"
                                        ]
                                        topics['acl_subscribe_all'] = []
                                        topics['acl_publish'] = {deviceid: []}
                                        topics['acl_subscribe'] = {
                                            deviceid:
                                            [f"{username}/{deviceid}/#"]
                                        }
                                        yield from self.add_acl(
                                            username, deviceid, topics)
                                elif callback == "add_acl":  # username/config add_acl publish/subscribe all/deviceid topic
                                    pubsub = args[0]
                                    deviceid = args[1]
                                    topic = args[2]

                                    if (topic.split('/')[0] != username):
                                        self.logger.error(
                                            "User attempting write to ACL outside namespace"
                                        )
                                        break

                                    # TODO: what should the perms for this be?
                                    topics = dict()
                                    topics['acl_publish_all'] = []
                                    topics['acl_subscribe_all'] = []
                                    topics['acl_publish'] = {}
                                    topics['acl_subscribe'] = {}
                                    if (pubsub == "publish"):
                                        if (deviceid == "all"):
                                            topics['acl_publish_all'].append(
                                                topic)
                                        else:
                                            topics['acl_publish'][deviceid] = [
                                                topic
                                            ]
                                    elif (pubsub == "subscribe"):
                                        if (deviceid == "all"):
                                            topics['acl_subscribe_all'].append(
                                                topic)
                                        else:
                                            topics['acl_subscribe'][
                                                deviceid] = [topic]

                                    yield from self.add_acl(
                                        username, client_deviceid, topics)
                                else:
                                    self.logger.info("Command not supported!")
                            except IndexError:
                                self.logger.error(
                                    "Wrong config callback format")
                            break

                        yield from self.plugins_manager.fire_event(
                            EVENT_BROKER_MESSAGE_RECEIVED,
                            client_id=client_session.client_id,
                            message=app_message)
                        yield from self._broadcast_message(
                            client_session, app_message.topic,
                            app_message.data)
                        if app_message.publish_packet.retain_flag:
                            self.retain_message(client_session,
                                                app_message.topic,
                                                app_message.data,
                                                app_message.qos)
                        wait_deliver = asyncio.Task(
                            handler.mqtt_deliver_next_message(),
                            loop=self._loop)
            except asyncio.CancelledError:
                self.logger.debug("Client loop cancelled")
                break
        disconnect_waiter.cancel()
        subscribe_waiter.cancel()
        unsubscribe_waiter.cancel()
        wait_deliver.cancel()

        self.logger.debug("%s Client disconnected" % client_session.client_id)
        server.release_connection()
예제 #31
0
 async def _broadcast_loop(self):
     running_tasks = deque()
     try:
         while True:
             while running_tasks and running_tasks[0].done():
                 task = running_tasks.popleft()
                 try:
                     task.result()  # make asyncio happy and collect results
                 except Exception:
                     pass
             broadcast = await self._broadcast_queue.get()
             if self.logger.isEnabledFor(logging.DEBUG):
                 self.logger.debug("broadcasting %r" % broadcast)
             for k_filter in self._subscriptions:
                 if broadcast["topic"].startswith("$") and (
                     k_filter.startswith("+") or k_filter.startswith("#")
                 ):
                     self.logger.debug(
                         "[MQTT-4.7.2-1] - ignoring brodcasting $ topic to subscriptions starting with + or #"
                     )
                 elif self.matches(broadcast["topic"], k_filter):
                     subscriptions = self._subscriptions[k_filter]
                     for (target_session, qos) in subscriptions:
                         if "qos" in broadcast:
                             qos = broadcast["qos"]
                         if target_session.transitions.state == "connected":
                             self.logger.debug(
                                 "broadcasting application message from %s on topic '%s' to %s"
                                 % (
                                     format_client_message(
                                         session=broadcast["session"]
                                     ),
                                     broadcast["topic"],
                                     format_client_message(session=target_session),
                                 )
                             )
                             handler = self._get_handler(target_session)
                             task = asyncio.ensure_future(
                                 handler.mqtt_publish(
                                     broadcast["topic"],
                                     broadcast["data"],
                                     qos,
                                     retain=False,
                                 ),
                                 loop=self._loop,
                             )
                             running_tasks.append(task)
                         else:
                             self.logger.debug(
                                 "retaining application message from %s on topic '%s' to client '%s'"
                                 % (
                                     format_client_message(
                                         session=broadcast["session"]
                                     ),
                                     broadcast["topic"],
                                     format_client_message(session=target_session),
                                 )
                             )
                             retained_message = RetainedApplicationMessage(
                                 broadcast["session"],
                                 broadcast["topic"],
                                 broadcast["data"],
                                 qos,
                             )
                             await target_session.retained_messages.put(
                                 retained_message
                             )
                             if self.logger.isEnabledFor(logging.DEBUG):
                                 self.logger.debug(
                                     f"target_session.retained_messages={target_session.retained_messages.qsize()}"
                                 )
     except CancelledError:
         # Wait until current broadcasting tasks end
         if running_tasks:
             await asyncio.wait(running_tasks, loop=self._loop)
         raise  # reraise per CancelledError semantics
예제 #32
0
    def init_from_connect(cls, reader: ReaderAdapter, writer: WriterAdapter, plugins_manager, loop=None):
        """

        :param reader:
        :param writer:
        :param plugins_manager:
        :param loop:
        :return:
        """
        remote_address, remote_port = writer.get_peer_info()
        connect = yield from ConnectPacket.from_stream(reader)
        yield from plugins_manager.fire_event(EVENT_MQTT_PACKET_RECEIVED, packet=connect)
        if connect.payload.client_id is None:
            raise MQTTException('[[MQTT-3.1.3-3]] : Client identifier must be present' )

        if connect.variable_header.will_flag:
            if connect.payload.will_topic is None or connect.payload.will_message is None:
                raise MQTTException('will flag set, but will topic/message not present in payload')

        if connect.variable_header.reserved_flag:
            raise MQTTException('[MQTT-3.1.2-3] CONNECT reserved flag must be set to 0')
        if connect.proto_name != "MQTT":
            raise MQTTException('[MQTT-3.1.2-1] Incorrect protocol name: "%s"' % connect.proto_name)

        connack = None
        error_msg = None
        if connect.proto_level != 4:
            # only MQTT 3.1.1 supported
            error_msg = 'Invalid protocol from %s: %d' % \
                              (format_client_message(address=remote_address, port=remote_port),
                               connect.variable_header.protocol_level)
            connack = ConnackPacket.build(0, UNACCEPTABLE_PROTOCOL_VERSION)  # [MQTT-3.2.2-4] session_parent=0
        elif connect.username_flag and connect.username is None:
            error_msg = 'Invalid username from %s' % \
                              (format_client_message(address=remote_address, port=remote_port))
            connack = ConnackPacket.build(0, BAD_USERNAME_PASSWORD)  # [MQTT-3.2.2-4] session_parent=0
        elif connect.password_flag and connect.password is None:
            error_msg = 'Invalid password %s' % (format_client_message(address=remote_address, port=remote_port))
            connack = ConnackPacket.build(0, BAD_USERNAME_PASSWORD)  # [MQTT-3.2.2-4] session_parent=0
        elif connect.clean_session_flag is False and connect.payload.client_id is None:
            error_msg = '[MQTT-3.1.3-8] [MQTT-3.1.3-9] %s: No client Id provided (cleansession=0)' % \
                              format_client_message(address=remote_address, port=remote_port)
            connack = ConnackPacket.build(0, IDENTIFIER_REJECTED)
        if connack is not None:
            yield from plugins_manager.fire_event(EVENT_MQTT_PACKET_SENT, packet=connack)
            yield from connack.to_stream(writer)
            yield from writer.close()
            raise MQTTException(error_msg)

        incoming_session = Session(loop)
        incoming_session.client_id = connect.client_id
        incoming_session.clean_session = connect.clean_session_flag
        incoming_session.will_flag = connect.will_flag
        incoming_session.will_retain = connect.will_retain_flag
        incoming_session.will_qos = connect.will_qos
        incoming_session.will_topic = connect.will_topic
        incoming_session.will_message = connect.will_message
        incoming_session.username = connect.username
        incoming_session.password = connect.password
        if connect.keep_alive > 0:
            incoming_session.keep_alive = connect.keep_alive
        else:
            incoming_session.keep_alive = 0

        handler = cls(plugins_manager, loop=loop)
        return handler, incoming_session
예제 #33
0
    async def init_from_connect(cls, stream: StreamAdapter, plugins_manager):
        """

        :param stream:
        :param plugins_manager:
        :return:
        """
        remote_address, remote_port = stream.get_peer_info()
        try:
            connect = await ConnectPacket.from_stream(stream)
        except NoDataException:
            raise MQTTException('Client closed the connection')
        logger.debug("< B %r", connect)
        await plugins_manager.fire_event(EVENT_MQTT_PACKET_RECEIVED,
                                         packet=connect)
        #this shouldn't be required anymore since broker generates for each client a random client_id if not provided
        #[MQTT-3.1.3-6]
        if connect.payload.client_id is None:
            raise MQTTException(
                '[[MQTT-3.1.3-3]] : Client identifier must be present')

        if connect.variable_header.will_flag:
            if connect.payload.will_topic is None or connect.payload.will_message is None:
                raise MQTTException(
                    'will flag set, but will topic/message not present in payload'
                )

        if connect.variable_header.reserved_flag:
            raise MQTTException(
                '[MQTT-3.1.2-3] CONNECT reserved flag must be set to 0')
        if connect.proto_name != "MQTT":
            raise MQTTException(
                '[MQTT-3.1.2-1] Incorrect protocol name: "%s"' %
                connect.proto_name)

        connack = None
        error_msg = None
        if connect.proto_level != 4:
            # only MQTT 3.1.1 supported
            error_msg = 'Invalid protocol from %s: %d' % (
                format_client_message(address=remote_address,
                                      port=remote_port), connect.proto_level)
            connack = ConnackPacket.build(0, UNACCEPTABLE_PROTOCOL_VERSION
                                          )  # [MQTT-3.2.2-4] session_parent=0
        elif not connect.username_flag and connect.password_flag:
            connack = ConnackPacket.build(
                0, BAD_USERNAME_PASSWORD)  # [MQTT-3.1.2-22]
        elif connect.username_flag and not connect.password_flag:
            connack = ConnackPacket.build(
                0, BAD_USERNAME_PASSWORD)  # [MQTT-3.1.2-22]
        elif connect.username_flag and connect.username is None:
            error_msg = 'Invalid username from %s' % (format_client_message(
                address=remote_address, port=remote_port))
            connack = ConnackPacket.build(
                0, BAD_USERNAME_PASSWORD)  # [MQTT-3.2.2-4] session_parent=0
        elif connect.password_flag and connect.password is None:
            error_msg = 'Invalid password %s' % (format_client_message(
                address=remote_address, port=remote_port))
            connack = ConnackPacket.build(
                0, BAD_USERNAME_PASSWORD)  # [MQTT-3.2.2-4] session_parent=0
        elif connect.clean_session_flag is False and (
                connect.payload.client_id_is_random):
            error_msg = '[MQTT-3.1.3-8] [MQTT-3.1.3-9] %s: No client Id provided (cleansession=0)' % (
                format_client_message(address=remote_address,
                                      port=remote_port))
            connack = ConnackPacket.build(0, IDENTIFIER_REJECTED)
        if connack is not None:
            logger.debug("B > %r", connack)
            await plugins_manager.fire_event(EVENT_MQTT_PACKET_SENT,
                                             packet=connack)
            await connack.to_stream(stream)

            await stream.close()
            raise MQTTException(error_msg)

        incoming_session = Session(plugins_manager)
        incoming_session.client_id = connect.client_id
        incoming_session.clean_session = connect.clean_session_flag
        incoming_session.will_flag = connect.will_flag
        incoming_session.will_retain = connect.will_retain_flag
        incoming_session.will_qos = connect.will_qos
        incoming_session.will_topic = connect.will_topic
        incoming_session.will_message = connect.will_message
        incoming_session.username = connect.username
        incoming_session.password = connect.password
        if connect.keep_alive > 0:
            incoming_session.keep_alive = connect.keep_alive
        else:
            incoming_session.keep_alive = 0

        handler = cls(plugins_manager)
        return handler, incoming_session
예제 #34
0
    def client_connected(self, listener_name, reader: ReaderAdapter,
                         writer: WriterAdapter):
        # Wait for connection available
        server = self._servers[listener_name]
        yield from server.acquire_connection()

        remote_address, remote_port = writer.get_peer_info()
        self.logger.debug("Connection from %s:%d on listener '%s'" %
                          (remote_address, remote_port, listener_name))

        # Wait for first packet and expect a CONNECT
        connect = None
        try:
            connect = yield from ConnectPacket.from_stream(reader)
            self.logger.debug(" <-in-- " + repr(connect))
            self.check_connect(connect)
        except HBMQTTException as exc:
            self.logger.warn(
                "[MQTT-3.1.0-1] %s: Can't read first packet an CONNECT: %s" %
                (format_client_message(address=remote_address,
                                       port=remote_port), exc))
            yield from writer.close()
            self.logger.debug("Connection closed")
            return
        except BrokerException as be:
            self.logger.error('Invalid connection from %s : %s' %
                              (format_client_message(address=remote_address,
                                                     port=remote_port), be))
            yield from writer.close()
            self.logger.debug("Connection closed")
            return

        connack = None
        if connect.variable_header.proto_level != 4:
            # only MQTT 3.1.1 supported
            self.logger.error(
                'Invalid protocol from %s: %d' % (format_client_message(
                    address=remote_address,
                    port=remote_port), connect.variable_header.protocol_level))
            connack = ConnackPacket.build(0, UNACCEPTABLE_PROTOCOL_VERSION
                                          )  # [MQTT-3.2.2-4] session_parent=0
        elif connect.variable_header.username_flag and connect.payload.username is None:
            self.logger.error('Invalid username from %s' %
                              (format_client_message(address=remote_address,
                                                     port=remote_port)))
            connack = ConnackPacket.build(
                0, BAD_USERNAME_PASSWORD)  # [MQTT-3.2.2-4] session_parent=0
        elif connect.variable_header.password_flag and connect.payload.password is None:
            self.logger.error('Invalid password %s' % (format_client_message(
                address=remote_address, port=remote_port)))
            connack = ConnackPacket.build(
                0, BAD_USERNAME_PASSWORD)  # [MQTT-3.2.2-4] session_parent=0
        elif connect.variable_header.clean_session_flag is False and connect.payload.client_id is None:
            self.logger.error(
                '[MQTT-3.1.3-8] [MQTT-3.1.3-9] %s: No client Id provided (cleansession=0)'
                % format_client_message(address=remote_address,
                                        port=remote_port))
            connack = ConnackPacket.build(0, IDENTIFIER_REJECTED)
            self.logger.debug(" -out-> " + repr(connack))
        if connack is not None:
            self.logger.debug(" -out-> " + repr(connack))
            yield from connack.to_stream(writer)
            yield from writer.close()
            return

        client_session = None
        self.logger.debug("Clean session={0}".format(
            connect.variable_header.clean_session_flag))
        self.logger.debug("known sessions={0}".format(self._sessions))
        client_id = connect.payload.client_id
        if connect.variable_header.clean_session_flag:
            # Delete existing session and create a new one
            if client_id is not None:
                self.delete_session(client_id)
            client_session = Session()
            client_session.parent = 0
            client_session.client_id = client_id
            self._sessions[client_id] = client_session
        else:
            # Get session from cache
            if client_id in self._sessions:
                self.logger.debug("Found old session %s" %
                                  repr(self._sessions[client_id]))
                client_session = self._sessions[client_id]
                client_session.parent = 1
            else:
                client_session = Session()
                client_session.client_id = client_id
                self._sessions[client_id] = client_session
                client_session.parent = 0

        if client_session.client_id is None:
            # Generate client ID
            client_session.client_id = gen_client_id()
        client_session.remote_address = remote_address
        client_session.remote_port = remote_port
        client_session.clean_session = connect.variable_header.clean_session_flag
        client_session.will_flag = connect.variable_header.will_flag
        client_session.will_retain = connect.variable_header.will_retain_flag
        client_session.will_qos = connect.variable_header.will_qos
        client_session.will_topic = connect.payload.will_topic
        client_session.will_message = connect.payload.will_message
        client_session.username = connect.payload.username
        client_session.password = connect.payload.password
        client_session.client_id = connect.payload.client_id
        if connect.variable_header.keep_alive > 0:
            client_session.keep_alive = connect.variable_header.keep_alive + self.config[
                'timeout-disconnect-delay']
        else:
            client_session.keep_alive = 0
        client_session.publish_retry_delay = self.config['publish-retry-delay']

        client_session.reader = reader
        client_session.writer = writer

        if self.authenticate(client_session):
            connack = ConnackPacket.build(client_session.parent,
                                          CONNECTION_ACCEPTED)
            self.logger.info('%s : connection accepted' %
                             format_client_message(session=client_session))
            self.logger.debug(" -out-> " + repr(connack))
            yield from connack.to_stream(writer)
        else:
            connack = ConnackPacket.build(client_session.parent,
                                          NOT_AUTHORIZED)
            self.logger.info('%s : connection refused' %
                             format_client_message(session=client_session))
            self.logger.debug(" -out-> " + repr(connack))
            yield from connack.to_stream(writer)
            yield from writer.close()
            return

        client_session.transitions.connect()
        handler = self._init_handler(reader, writer, client_session)
        self.logger.debug("%s Start messages handling" %
                          client_session.client_id)
        yield from handler.start()
        self.logger.debug("Retained messages queue size: %d" %
                          client_session.retained_messages.qsize())
        yield from self.publish_session_retained_messages(client_session)
        self.logger.debug("%s Wait for disconnect" % client_session.client_id)

        connected = True
        wait_disconnect = asyncio.Task(handler.wait_disconnect())
        wait_subscription = asyncio.Task(
            handler.get_next_pending_subscription())
        wait_unsubscription = asyncio.Task(
            handler.get_next_pending_unsubscription())
        wait_deliver = asyncio.Task(handler.mqtt_deliver_next_message())
        while connected:
            done, pending = yield from asyncio.wait(
                [
                    wait_disconnect, wait_subscription, wait_unsubscription,
                    wait_deliver
                ],
                return_when=asyncio.FIRST_COMPLETED)
            if wait_disconnect in done:
                result = wait_disconnect.result()
                self.logger.debug("%s Result from wait_diconnect: %s" %
                                  (client_session.client_id, result))
                if result is None:
                    self.logger.debug("Will flag: %s" %
                                      client_session.will_flag)
                    # Connection closed anormally, send will message
                    if client_session.will_flag:
                        self.logger.debug(
                            "Client %s disconnected abnormally, sending will message"
                            % format_client_message(client_session))
                        yield from self.broadcast_application_message(
                            client_session, client_session.will_topic,
                            client_session.will_message,
                            client_session.will_qos)
                        if client_session.will_retain:
                            self.retain_message(client_session,
                                                client_session.will_topic,
                                                client_session.will_message,
                                                client_session.will_qos)
                connected = False
            if wait_unsubscription in done:
                self.logger.debug("%s handling unsubscription" %
                                  client_session.client_id)
                unsubscription = wait_unsubscription.result()
                for topic in unsubscription['topics']:
                    self.del_subscription(topic, client_session)
                yield from handler.mqtt_acknowledge_unsubscription(
                    unsubscription['packet_id'])
                wait_unsubscription = asyncio.Task(
                    handler.get_next_pending_unsubscription())
            if wait_subscription in done:
                self.logger.debug("%s handling subscription" %
                                  client_session.client_id)
                subscriptions = wait_subscription.result()
                return_codes = []
                for subscription in subscriptions['topics']:
                    return_codes.append(
                        self.add_subscription(subscription, client_session))
                yield from handler.mqtt_acknowledge_subscription(
                    subscriptions['packet_id'], return_codes)
                for index, subscription in enumerate(subscriptions['topics']):
                    if return_codes[index] != 0x80:
                        yield from self.publish_retained_messages_for_subscription(
                            subscription, client_session)
                wait_subscription = asyncio.Task(
                    handler.get_next_pending_subscription())
                self.logger.debug(repr(self._subscriptions))
            if wait_deliver in done:
                self.logger.debug("%s handling message delivery" %
                                  client_session.client_id)
                publish_packet = wait_deliver.result()
                packet_id = publish_packet.variable_header.packet_id
                topic_name = publish_packet.variable_header.topic_name
                data = publish_packet.payload.data
                yield from self.broadcast_application_message(
                    client_session, topic_name, data)
                if publish_packet.retain_flag:
                    self.retain_message(client_session, topic_name, data)
                # Acknowledge message delivery
                yield from handler.mqtt_acknowledge_delivery(packet_id)
                wait_deliver = asyncio.Task(
                    handler.mqtt_deliver_next_message())
        wait_subscription.cancel()
        wait_unsubscription.cancel()
        wait_deliver.cancel()

        self.logger.debug("%s Client disconnecting" % client_session.client_id)
        yield from self._stop_handler(handler)
        client_session.transitions.disconnect()
        yield from writer.close()
        self.logger.debug("%s Session disconnected" % client_session.client_id)
        server.release_connection()