def test_single_level_match(self): sub_ereg = MQTTUtils.convert_to_ereg("foo/+/bar") self.assertIsNotNone(re.match(sub_ereg, 'foo/buzz/bar')) self.assertIsNotNone(re.match(sub_ereg, 'foo//bar')) self.assertIsNone(re.match(sub_ereg, 'foo/bar')) self.assertIsNone(re.match(sub_ereg, 'foo/bar/')) self.assertIsNone(re.match(sub_ereg, '/foo/bar')) self.assertIsNone(re.match(sub_ereg, 'foo/one/two/bar')) self.assertIsNone(re.match(sub_ereg, 'foo/one/bar/')) self.assertIsNone(re.match(sub_ereg, '/foo/one/bar')) self.assertIsNone(re.match(sub_ereg, 'foo/+/bar')) ereg = MQTTUtils.convert_to_ereg('foo/bar/+') self.assertIsNotNone(re.match(ereg, 'foo/bar/buzz')) self.assertIsNotNone(re.match(ereg, 'foo/bar/')) self.assertIsNone(re.match(ereg, 'foo/bar/+')) self.assertIsNone(re.match(ereg, 'foo/bar/#')) self.assertIsNone(re.match(ereg, 'foo/bar/+/')) self.assertIsNone(re.match(ereg, 'foo/bar/+/#')) ereg = MQTTUtils.convert_to_ereg('+/foo/bar') self.assertIsNotNone(re.match(ereg, 'buzz/foo/bar')) self.assertIsNotNone(re.match(ereg, '/foo/bar')) self.assertIsNone(re.match(ereg, 'foo/bar')) self.assertIsNone(re.match(ereg, '//foo/bar'))
def _encode_data(self): buffer = bytearray() buffer.extend(MQTTUtils.encode_value(self.id)) for intent in self.subscription_intents: topic, qos = intent buffer.extend(MQTTUtils.encode_string(topic)) buffer.append(MQTTUtils.encode_byte(qos) & self.QOS_PART_MASK) return bytes(buffer)
def _encode_data(self): buffer = bytearray() buffer.extend(MQTTUtils.encode_string(self.topic)) if self.qos in [MQTTConstants.AT_LEAST_ONCE, MQTTConstants.EXACTLY_ONCE]: buffer.extend(MQTTUtils.encode_value(self.id)) buffer.extend(self.payload) return bytes(buffer)
def _decode_data(self, _data): self.topic, l = MQTTUtils.decode_string(_data) cursor = l + 2 if self.qos > MQTTConstants.AT_MOST_ONCE: self.id = MQTTUtils.decode_value(_data[cursor:cursor + 2]) cursor += 2 else: self.id = None self.payload = _data[cursor:]
def _decode_data(self, _data): cursor = 0 self.id = MQTTUtils.decode_value(_data[cursor:cursor + 2]) cursor += 2 self.unsubscribe_list = [] while cursor < self.length: topic, l = MQTTUtils.decode_string(_data[cursor:]) cursor += l + 2 self.unsubscribe_list.append(topic)
def _decode_data(self, _data): self.id = MQTTUtils.decode_value(_data[0:2]) cursor = 2 self.subscription_intents = [] while cursor < self.length: topic, l = MQTTUtils.decode_string(_data[cursor:]) cursor += l + 2 qos = _data[cursor] & 0x03 cursor += 1 self.subscription_intents.append((topic, qos))
def _encode_fixed_header(self, data): buffer = bytearray(1) buffer[0] |= self.type << 4 buffer[0] |= (self.dup or 0) << 3 buffer[0] |= (self.qos or 0) << 1 buffer[0] |= self.retain or 0 if not data: buffer.extend(MQTTUtils.encode_length(0)) else: buffer.extend(MQTTUtils.encode_length(len(data))) return bytearray(buffer)
def _encode_data(self): buffer = bytearray() # variable header buffer.extend(MQTTUtils.encode_string(self.protocol_name)) buffer.append(MQTTUtils.encode_byte(self.protocol_version)) connect_flags = 0x00 connect_flags |= 0x80 if self.has_username else 0x00 connect_flags |= 0x40 if self.has_passwd else 0x00 connect_flags |= 0x20 if self.will_retain else 0x00 connect_flags |= ((self.will_qos or 0x00) << 3) & 0x18 connect_flags |= 0x04 if self.will_flag else 0x00 connect_flags |= 0x02 if self.clean_session else 0x00 buffer.append(connect_flags) buffer.extend(MQTTUtils.encode_value(self.keep_alive)) # payload buffer.extend(MQTTUtils.encode_string(self.client_uid)) if self.will_flag: buffer.extend(MQTTUtils.encode_string(self.will_topic)) buffer.extend(MQTTUtils.encode_string(self.will_message)) if self.has_username: buffer.extend(MQTTUtils.encode_string(self.username)) if self.has_passwd: buffer.extend(MQTTUtils.encode_string(self.passwd)) return bytes(buffer)
def test_invalid_subscription_masks(self): masks = ( '##', '++', '#/', '/#/+', '+#', '#+', '+#/', '#+/', '/+#', '/#+', 'sports+', 'sports#', '+sports', '#sports', 'sports/#/', 'sport/tennis#', 'sport#/tennis', '#sport/tennis', 'sport/#tennis', 'sport/tennis+', 'sport+/tennis', 'sport/+tennis', '+sport/tennis', '++/sport/tennis', 'sport/++/tennis', 'sport/tennis/++', '+++/sport/tennis', 'sport/+++/tennis', 'sport/tennis/+++', 'sport/tennis/#/ranking', 'sport/tennis/##/ranking', '#/sport/tennis/ranking', '##/sport/tennis/ranking', 'sport/tennis/ranking/##', 'sport/tennis/ranking/##/', ) for mask in masks: self.assertFalse(MQTTUtils.subscription_is_valid(mask), "%s is valid" % mask)
def test_single_level_mask(self): single_level = MQTTUtils.convert_to_ereg('foo/+/bar', allow_wildcards=True) self.assertIsMatch(single_level, 'foo/+/bar') self.assertIsMatch(single_level, 'foo/buzz/bar') self.assertIsMatch(single_level, 'foo//bar') self.assertIsNotMatch(single_level, 'foo/bar')
def _clear_authorization_entry(cls, ts, allow_wildcards=False): """ Validates an authorization entry :param ts: authorization entry: an asterisk string `'*'` meaning fully authorized, or a list of strings (topics, wildcards allowed) :type ts: str | list[str] :return: """ if ts == cls.ALL: return ts elif isinstance(ts, list): rs = [] for t in ts: assert isinstance(t, str) ereg = MQTTUtils.convert_to_ereg( t, allow_wildcards=allow_wildcards) assert isinstance(ereg, str) rs.append((t, re.compile(ereg))) return rs else: raise ValueError('authorization has unexpected format')
def _clear_authorization_entry(cls, ts, allow_wildcards=False): """ Validates an authorization entry :param ts: authorization entry: an asterisk string `'*'` meaning fully authorized, or a list of strings (topics, wildcards allowed) :type ts: str | list[str] :return: """ if ts == cls.ALL: return ts elif isinstance(ts, list): rs = [] for t in ts: assert isinstance(t, str) ereg = MQTTUtils.convert_to_ereg(t, allow_wildcards=allow_wildcards) assert isinstance(ereg, str) rs.append((t, re.compile(ereg))) return rs else: raise ValueError('authorization has unexpected format')
def read_message(self): buffer = bytearray() chunk = yield self.read_bytes_async(MQTTConstants.MESSAGE_FIXED_HEADER_MINIMUM_SIZE) buffer.extend(chunk) while not MQTTUtils.is_length_field_complete(buffer[MQTTConstants.MESSAGE_TYPE_LENGTH:]): chunk = yield self.read_bytes_async(1) buffer.extend(chunk) msg_length, field_size = MQTTUtils.decode_length(buffer[MQTTConstants.MESSAGE_TYPE_LENGTH:]) if msg_length > 0: chunk = yield self.read_bytes_async(msg_length) buffer.extend(chunk) self._update_timeout() return bytes(buffer)
def _get_regex(self, mask): compiled = self._re_cache.get(mask, None) if not compiled: ereg = MQTTUtils.convert_to_ereg(mask) compiled = re.compile(ereg) self._re_cache[mask] = compiled return compiled
def test_substring_doesnt_match(self): super = "/foo/bar" sub = "/foo/ba" sub_ereg = MQTTUtils.convert_to_ereg(sub) self.assertTrue(re.match(sub_ereg, sub) is not None) self.assertTrue(re.match(sub_ereg, super) is None) ereg = MQTTUtils.convert_to_ereg('foo/bar') self.assertIsNotNone(re.match(ereg, 'foo/bar')) self.assertIsNone(re.match(ereg, 'foo/bar/')) self.assertIsNone(re.match(ereg, '/foo/bar')) self.assertIsNone(re.match(ereg, 'foo//bar')) self.assertIsNone(re.match(ereg, 'foo/bar/buzz')) self.assertIsNone(re.match(ereg, 'buzz/foo/bar')) self.assertIsNone(re.match(ereg, 'foo/buzz/bar'))
def read_message(self): buffer = bytearray() chunk = yield self.read_bytes_async( MQTTConstants.MESSAGE_FIXED_HEADER_MINIMUM_SIZE) buffer.extend(chunk) while not MQTTUtils.is_length_field_complete( buffer[MQTTConstants.MESSAGE_TYPE_LENGTH:]): chunk = yield self.read_bytes_async(1) buffer.extend(chunk) msg_length, field_size = MQTTUtils.decode_length( buffer[MQTTConstants.MESSAGE_TYPE_LENGTH:]) if msg_length > 0: chunk = yield self.read_bytes_async(msg_length) buffer.extend(chunk) self._update_timeout() return bytes(buffer)
def test_multilevel_match(self): sub = "foo/bar/#" sub_ereg = MQTTUtils.convert_to_ereg(sub) self.assertIsNone(re.match(sub_ereg, 'foo/b')) self.assertIsNotNone(re.match(sub_ereg, 'foo/bar')) self.assertIsNotNone(re.match(sub_ereg, 'foo/bar/')) self.assertIsNotNone(re.match(sub_ereg, 'foo/bar/one')) self.assertIsNotNone(re.match(sub_ereg, 'foo/bar/one/')) self.assertIsNotNone(re.match(sub_ereg, 'foo/bar/one/two')) self.assertIsNotNone(re.match(sub_ereg, 'foo/bar/one/two/')) self.assertIsNone(re.match(sub_ereg, 'foo/bar/#')) self.assertIsNone(re.match(sub_ereg, 'foo/bar/+')) self.assertIsNone(re.match(sub_ereg, 'foo/bar/+/')) self.assertIsNone(re.match(sub_ereg, 'foo/bar/+/#'))
def _decode_data(self, _data): self.protocol_name, l = MQTTUtils.decode_string(_data) assert self.protocol_name == 'MQIsdp' or self.protocol_name == 'MQTT' cursor = l + 2 self.protocol_version = _data[cursor] assert self.protocol_version == 3 or self.protocol_version == 4 cursor += 1 self.has_username = (_data[cursor] & 0x80) == 0x80 self.has_passwd = (_data[cursor] & 0x40) == 0x40 self.will_retain = (_data[cursor] & 0x20) == 0x20 self.will_qos = (_data[cursor] & 0x18) >> 3 self.will_flag = (_data[cursor] & 0x04) == 0x04 self.clean_session = (_data[cursor] & 0x02) == 0x02 cursor += 1 self.keep_alive = MQTTUtils.decode_value(_data[cursor:cursor+2]) cursor += 2 self.client_uid, l = MQTTUtils.decode_string(_data[cursor:]) cursor += l if self.will_flag: cursor += 2 self.will_topic, l = MQTTUtils.decode_string(_data[cursor:]) cursor += l + 2 self.will_message, l = MQTTUtils.decode_string(_data[cursor:]) cursor += l else: self.will_topic = None self.will_message = None if self.has_username: cursor += 2 self.username, l = MQTTUtils.decode_string(_data[cursor:]) cursor += l else: self.username = None if self.has_passwd: cursor += 2 if cursor < self.length - 2: self.passwd, l = MQTTUtils.decode_string(_data[cursor:]) else: self.passwd = None else: self.passwd = None
def test_multi_level_mask(self): """ tests for access_control.Authorization.is_subscription_allowed """ multi_level = MQTTUtils.convert_to_ereg('foo/bar/#', allow_wildcards=True) self.assertIsMatch(multi_level, 'foo/bar') self.assertIsMatch(multi_level, 'foo/bar/') self.assertIsMatch(multi_level, 'foo/bar//') self.assertIsMatch(multi_level, 'foo/bar/#') self.assertIsMatch(multi_level, 'foo/bar/+') self.assertIsMatch(multi_level, 'foo/bar/+/') self.assertIsMatch(multi_level, 'foo/bar/+/#') self.assertIsMatch(multi_level, 'foo/bar/buzz/+/#') self.assertIsMatch(multi_level, 'foo/bar/fuzz/+/buzz/#')
def from_bytes(cls, bytes_): assert type(bytes_) == bytes obj = cls() _raw_data = bytes(bytes_) obj.type, obj.dup, obj.qos, obj.retain, obj.length, remaining_data \ = MQTTUtils.strip_fixed_header(_raw_data) assert obj.type == obj._message_type obj._decode_data(remaining_data) # The raw_data already corresponds to the obj field data. We change it # back to false to avoid encoding the data again upon raw_data lookup. obj._raw_data = _raw_data obj._pending_update = False return obj
def test_valid_subscription_masks(self): masks = ( '', '#', 'foo/#', '1/data/#', '1/control/user/2', '1/control/user/+', '1/control/+/2', '+/control/+/2', '+/control/+/#', '1/control/devices/wtcsctnfZ-1-2', '+', '+/', '+/#', '+/+', '+/+/', '+/+/#', '/+', '/+/+', '/+/+/#', 'sport/tennis/player1', 'sport/tennis/player1/ranking', 'sport/tennis/player1/score/wimbledon', 'sport/#', 'sport/tennis/#', '+/tennis/#', 'sport/+/player1', 'sport/+/player1/#', '/', '//', '+//', '/+/', '//+', '/#', '//#', '+//#', '/+/#', '//+/#', ) for mask in masks: self.assertTrue(MQTTUtils.subscription_is_valid(mask), "%s is invalid" % mask)
def test_mixed_masks(self): mixed = MQTTUtils.convert_to_ereg('foo/+/bar/#', allow_wildcards=True) self.assertIsMatch(mixed, 'foo/+/bar') self.assertIsMatch(mixed, 'foo/+/bar/#') self.assertIsMatch(mixed, 'foo/+/bar/buzz') self.assertIsMatch(mixed, 'foo/+/bar/buzz/') self.assertIsMatch(mixed, 'foo/+/bar/buzz/#') self.assertIsMatch(mixed, 'foo/+/bar/+/#') self.assertIsMatch(mixed, 'foo/+/bar/+/+/#') self.assertIsMatch(mixed, 'foo/buzz/bar') self.assertIsMatch(mixed, 'foo/buzz/bar/') self.assertIsMatch(mixed, 'foo/buzz/bar/+') self.assertIsMatch(mixed, 'foo/buzz/bar/#') self.assertIsMatch(mixed, 'foo/buzz/bar/+/') self.assertIsMatch(mixed, 'foo/buzz/bar/+/#') self.assertIsNotMatch(mixed, 'foo/#') self.assertIsNotMatch(mixed, 'foo/+/#') self.assertIsNotMatch(mixed, 'foo/+/+/#')
def subscribe(self, subscription_mask, qos): """ Subscribes the client to a topic or wildcarded mask at the informed QoS level. Calling this method also signalizes the server to enqueue the matching retained messages. When called for a (`subscripition_mask`, `qos`) pair for which the client has already a subscription it will silently ignore the command and return a suback. :param string subscription_mask: A MQTT valid topic or wildcarded mask; :param int qos: A valid QoS level (0, 1 or 2). :rtype: int :return: The granted QoS level (0, 1 or 2) or 0x80 for failed subscriptions. """ if qos not in [0, 1, 2]: self.logger.warn('client tried to subscribe with invalid qos %s' % qos) return 0x80 new_subscription = subscription_mask not in self.subscriptions or \ self.subscriptions.qos(subscription_mask) != qos if not self.authorization.is_subscription_allowed(subscription_mask): self.logger.warn("[uid: %s] is not allowed to subscribe on %s" % (self.uid, subscription_mask)) del self.subscriptions[subscription_mask] qos = 0x80 elif new_subscription: ereg = MQTTUtils.convert_to_ereg(subscription_mask) if ereg is not None: self.subscriptions.add(subscription_mask, qos, re.compile(ereg)) self.server.enqueue_retained_message(self, subscription_mask) else: qos = 0x80 if "#" in subscription_mask: print("SUBSCRIBING TO: {}".format(subscription_mask)) return qos
def test_mixed_match(self): ereg = MQTTUtils.convert_to_ereg('foo/+/bar/#') self.assertIsNotNone(re.match(ereg, 'foo/xyz/bar')) self.assertIsNotNone(re.match(ereg, 'foo/xyz/bar/')) self.assertIsNotNone(re.match(ereg, 'foo/xyz/bar/abc')) self.assertIsNotNone(re.match(ereg, 'foo//bar')) self.assertIsNotNone(re.match(ereg, 'foo//bar/')) self.assertIsNotNone(re.match(ereg, 'foo//bar/abc')) self.assertIsNone(re.match(ereg, 'foo/bar')) self.assertIsNone(re.match(ereg, 'foo/#')) self.assertIsNone(re.match(ereg, 'foo/+/bar')) self.assertIsNone(re.match(ereg, 'foo/+/bar/#')) self.assertIsNone(re.match(ereg, 'foo/+/bar/abc')) self.assertIsNone(re.match(ereg, 'foo/+/bar/abc/')) self.assertIsNone(re.match(ereg, 'foo/+/bar/abc/#')) self.assertIsNone(re.match(ereg, 'foo/+/bar/+/#')) self.assertIsNone(re.match(ereg, 'foo/xyz/bar/#')) self.assertIsNone(re.match(ereg, 'foo/xyz/bar/+')) self.assertIsNone(re.match(ereg, 'foo/xyz/bar/+/#'))
def add(self, mask, qos, pattern=None): self._re_cache[mask] = pattern or re.compile( MQTTUtils.convert_to_ereg(mask)) self._subscriptions[mask] = qos
def _decode_data(self, _data): cursor = 0 self.id = MQTTUtils.decode_value(_data[cursor:cursor + 2]) cursor += 2 self.payload = _data[cursor:]
def _encode_data(self): return bytes(MQTTUtils.encode_value(self.id))
def __hash__(self): return MQTTUtils.hash_message_bytes(self.raw_data)
def _decode_data(self, _data): self.id = MQTTUtils.decode_value(_data[0:2])
def _encode_data(self): buffer = bytearray() buffer.extend(self.id) for topic in self.unsubscribe_list: buffer.extend(MQTTUtils.encode_string(topic))
def add(self, mask, qos, pattern=None): self._re_cache[mask] = pattern or re.compile(MQTTUtils.convert_to_ereg(mask)) self._subscriptions[mask] = qos
def _encode_data(self): buffer = bytearray() buffer.extend(MQTTUtils.encode_value(self.id)) buffer.extend(self.payload) return bytes(buffer)
def _encode_data(self): buffer = bytearray() sp = self.SESSION_PRESENT if self.session_present else 0x00 buffer.append(MQTTUtils.encode_byte(sp)) buffer.append(MQTTUtils.encode_byte(self.return_code)) return bytes(buffer)