Example #1
0
    def _initsession(self, host=None, port=None, username=None, password=None, uri=None, cleansession=None) -> dict:
        # Load config
        broker_conf = self.config.get('broker', dict()).copy()
        if 'mqtt' not in broker_conf:
            broker_conf['scheme'] = 'mqtt'
        if 'username' not in broker_conf:
            broker_conf['username'] = None
        if 'password' not in broker_conf:
            broker_conf['password'] = None

        if uri is not None:
            result = urlparse(uri)
            if result.scheme:
                broker_conf['scheme'] = result.scheme
            if result.hostname:
                broker_conf['host'] = result.hostname
            if result.port:
                broker_conf['port'] = result.port
            if result.username:
                broker_conf['username'] = result.username
            if result.password:
                broker_conf['password'] = result.password
        if host:
            broker_conf['host'] = host
        if port:
            broker_conf['port'] = int(port)
        if username:
            broker_conf['username'] = username
        if password:
            broker_conf['password'] = password
        if cleansession is not None:
            broker_conf['cleansession'] = cleansession

        for key in ['scheme', 'host', 'port']:
            if not_in_dict_or_none(broker_conf, key):
                raise ClientException("Missing connection parameter '%s'" % key)

        s = Session()
        s.client_id = self.client_id
        s.remote_address = broker_conf['host']
        s.remote_port = broker_conf['port']
        s.username = broker_conf['username']
        s.password = broker_conf['password']
        s.scheme = broker_conf['scheme']
        if cleansession is not None:
            s.cleansession = cleansession
        else:
            s.cleansession = self.config.get('cleansession', True)
        s.keep_alive = self.config['keep_alive']
        if 'will' in self.config:
            s.will_flag = True
            s.will_retain = self.config['will']['retain']
            s.will_topic = self.config['will']['topic']
            s.will_message = self.config['will']['message']
        else:
            s.will_flag = False
            s.will_retain = False
            s.will_topic = None
            s.will_message = None
        return s
Example #2
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()
Example #3
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()