def __init__(self, client_id=None, config=None, loop=None): self.logger = logging.getLogger(__name__) self.config = _defaults if config is not None: self.config.update(config) if client_id is not None: self.client_id = client_id else: from hbmqtt.utils import gen_client_id self.client_id = gen_client_id() self.logger.debug("Using generated client ID : %s" % self.client_id) if loop is not None: self._loop = loop else: self._loop = asyncio.get_event_loop() self.session = None self._handler = None self._disconnect_task = None self._connected_state = asyncio.Event() # Init plugins manager context = ClientContext() context.config = self.config self.plugins_manager = PluginManager('hbmqtt.client.plugins', context) self.client_tasks = deque()
def __init__(self, client_id=None, config=None, loop=None): self.logger = logging.getLogger(__name__) self.config = copy.deepcopy(_defaults) if config is not None: self.config.update(config) if client_id is not None: self.client_id = client_id else: from hbmqtt.utils import gen_client_id self.client_id = gen_client_id() self.logger.debug("Using generated client ID : %s" % self.client_id) if loop is not None: self._loop = loop else: self._loop = asyncio.get_event_loop() self.session = None self._handler = None self._disconnect_task = None self._connected_state = asyncio.Event(loop=self._loop) self._no_more_connections = asyncio.Event(loop=self._loop) self.extra_headers = {} # Init plugins manager context = ClientContext() context.config = self.config self.plugins_manager = PluginManager("hbmqtt.client.plugins", context, loop=self._loop) self.client_tasks = deque()
def __init__(self, tg: anyio.abc.TaskGroup, client_id=None, config=None): self.logger = logging.getLogger(__name__) self.config = copy.deepcopy(_defaults) if config is not None: self.config.update(config) if client_id is not None: self.client_id = client_id else: from hbmqtt.utils import gen_client_id self.client_id = gen_client_id() self.logger.debug("Using generated client ID : %s", self.client_id) self.session = None self._tg = tg self._handler = None self._disconnect_task = None self._connected_state = anyio.create_event() self._no_more_connections = anyio.create_event() self.extra_headers = {} # Init plugins manager context = ClientContext() context.config = self.config self.plugins_manager = PluginManager(tg, 'hbmqtt.client.plugins', context) self.client_tasks = set()
def __init__(self, client_id=None, config=None, loop=None): """ :param config: Example yaml config broker: uri: mqtt:username@password//localhost:1883/ cafile: somefile.cert #Server authority file capath: /some/path # certficate file path cadata: certificate as string data keep_alive: 60 cleansession: true will: retain: false topic: some/topic message: Will message qos: 0 default_qos: 0 default_retain: false topics: a/b: qos: 2 retain: true :param loop: :return: """ self.logger = logging.getLogger(__name__) self.config = _defaults if config is not None: self.config.update(config) if client_id is not None: self.client_id = client_id else: from hbmqtt.utils import gen_client_id self.client_id = gen_client_id() self.logger.debug("Using generated client ID : %s" % self.client_id) if loop is not None: self._loop = loop else: self._loop = asyncio.get_event_loop() self.session = None self._handler = None self._disconnect_task = None self._connection_closed_future = None
async def from_stream( cls, reader: ReaderAdapter, fixed_header: MQTTFixedHeader, variable_header: ConnectVariableHeader, ): payload = cls() # Client identifier try: payload.client_id = await decode_string(reader) except NoDataException: payload.client_id = None if payload.client_id is None or payload.client_id == "": # A Server MAY allow a Client to supply a ClientId that has a length of zero bytes # [MQTT-3.1.3-6] payload.client_id = gen_client_id() # indicator to trow exception in case CLEAN_SESSION_FLAG is set to False payload.client_id_is_random = True # Read will topic, username and password if variable_header.will_flag: try: payload.will_topic = await decode_string(reader) payload.will_message = await decode_data_with_length(reader) except NoDataException: payload.will_topic = None payload.will_message = None if variable_header.username_flag: try: payload.username = await decode_string(reader) except NoDataException: payload.username = None if variable_header.password_flag: try: payload.password = await decode_string(reader) except NoDataException: payload.password = None return payload
def from_stream(cls, reader: ReaderAdapter, fixed_header: MQTTFixedHeader, variable_header: ConnectVariableHeader): payload = cls() # Client identifier try: payload.client_id = yield from decode_string(reader) except NoDataException: payload.client_id = None if (payload.client_id is None or payload.client_id == ""): # A Server MAY allow a Client to supply a ClientId that has a length of zero bytes # [MQTT-3.1.3-6] payload.client_id = gen_client_id() # indicator to trow exception in case CLEAN_SESSION_FLAG is set to False payload.client_id_is_random = True # Read will topic, username and password if variable_header.will_flag: try: payload.will_topic = yield from decode_string(reader) payload.will_message = yield from decode_data_with_length(reader) except NoDataException: payload.will_topic = None payload.will_message = None if variable_header.username_flag: try: payload.username = yield from decode_string(reader) except NoDataException: payload.username = None if variable_header.password_flag: try: payload.password = yield from decode_string(reader) except NoDataException: payload.password = None return payload
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()
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()
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()
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()
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)
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()