diff --git a/tests/test_uri/test_validator/test_urivalidator.py b/tests/test_uri/test_validator/test_urivalidator.py index 5c670ce..7e96294 100644 --- a/tests/test_uri/test_validator/test_urivalidator.py +++ b/tests/test_uri/test_validator/test_urivalidator.py @@ -14,6 +14,7 @@ import unittest +from uprotocol.uri.serializer.uriserializer import UriSerializer from uprotocol.uri.validator.urivalidator import UriValidator from uprotocol.v1.uri_pb2 import UUri @@ -31,7 +32,7 @@ def test_is_empty_with_non_empty_uri(self): def test_is_rpc_method_passing_null_for_uri(self): self.assertFalse(UriValidator.is_rpc_method(None)) - def test_is_rpc_method_passing_null_for_uri(self): + def test_is_rpc_method_passing_default_for_uri(self): self.assertFalse(UriValidator.is_rpc_method(UUri())) def test_is_rpc_method_for_uresource_id_less_than_min_topic(self): @@ -49,13 +50,83 @@ def test_is_rpc_method_none(self): self.assertFalse(UriValidator.is_rpc_method(None)) def test_is_rpc_response_with_resourceid_equal_rpcresponseid(self): - uri = UUri(authority_name="hi", resource_id=UriValidator.DEFAULT_RESOURCE_ID) + uri = UUri(authority_name="hi", resource_id=UriValidator.RESOURCE_ID_RESPONSE) self.assertTrue(UriValidator.is_rpc_response(uri)) def test_is_rpc_response_with_resourceid_greater_rpcresponseid(self): - uri = UUri(resource_id=UriValidator.DEFAULT_RESOURCE_ID + 1) + uri = UUri(resource_id=UriValidator.RESOURCE_ID_RESPONSE + 1) self.assertFalse(UriValidator.is_rpc_response(uri)) + def test_matches_succeeds_for_identical_uris(self): + pattern_uri = UriSerializer.deserialize("//authority/A410/3/1003") + candidate_uri = UriSerializer.deserialize("//authority/A410/3/1003") + self.assertTrue(UriValidator.matches(pattern_uri, candidate_uri)) + + def test_matches_succeeds_for_pattern_with_wildcard_authority(self): + pattern_uri = UriSerializer.deserialize("//*/A410/3/1003") + candidate_uri = UriSerializer.deserialize("//authority/A410/3/1003") + self.assertTrue(UriValidator.matches(pattern_uri, candidate_uri)) + + def test_matches_succeeds_for_pattern_with_wildcard_authority_and_local_candidate_uri(self): + pattern_uri = UriSerializer.deserialize("//*/A410/3/1003") + candidate_uri = UriSerializer.deserialize("/A410/3/1003") + self.assertTrue(UriValidator.matches(pattern_uri, candidate_uri)) + + def test_matches_succeeds_for_pattern_with_wildcard_entity_id(self): + pattern_uri = UriSerializer.deserialize("//authority/FFFF/3/1003") + candidate_uri = UriSerializer.deserialize("//authority/A410/3/1003") + self.assertTrue(UriValidator.matches(pattern_uri, candidate_uri)) + + def test_matches_succeeds_for_pattern_with_macthing_entity_instance(self): + pattern_uri = UriSerializer.deserialize("//authority/A410/3/1003") + candidate_uri = UriSerializer.deserialize("//authority/2A410/3/1003") + self.assertTrue(UriValidator.matches(pattern_uri, candidate_uri)) + + def test_matches_succeeds_for_pattern_with_wildcard_entity_version(self): + pattern_uri = UriSerializer.deserialize("//authority/A410/FF/1003") + candidate_uri = UriSerializer.deserialize("//authority/A410/3/1003") + self.assertTrue(UriValidator.matches(pattern_uri, candidate_uri)) + + def test_matches_succeeds_for_pattern_with_wildcard_resource(self): + pattern_uri = UriSerializer.deserialize("//authority/A410/3/FFFF") + candidate_uri = UriSerializer.deserialize("//authority/A410/3/1003") + self.assertTrue(UriValidator.matches(pattern_uri, candidate_uri)) + + def test_matches_fail_for_upper_case_authority(self): + pattern = UriSerializer.deserialize("//Authority/A410/3/1003") + candidate = UriSerializer.deserialize("//authority/A410/3/1003") + self.assertFalse(UriValidator.matches(pattern, candidate)) + + def test_matches_fail_for_local_pattern_with_authority(self): + pattern = UriSerializer.deserialize("/A410/3/1003") + candidate = UriSerializer.deserialize("//authority/A410/3/1003") + self.assertFalse(UriValidator.matches(pattern, candidate)) + + def test_matches_fail_for_different_authority(self): + pattern = UriSerializer.deserialize("//other/A410/3/1003") + candidate = UriSerializer.deserialize("//authority/A410/3/1003") + self.assertFalse(UriValidator.matches(pattern, candidate)) + + def test_matches_fail_for_different_entity_id(self): + pattern = UriSerializer.deserialize("//authority/45/3/1003") + candidate = UriSerializer.deserialize("//authority/A410/3/1003") + self.assertFalse(UriValidator.matches(pattern, candidate)) + + def test_matches_fail_for_different_entity_instance(self): + pattern = UriSerializer.deserialize("//authority/30A410/3/1003") + candidate = UriSerializer.deserialize("//authority/2A410/3/1003") + self.assertFalse(UriValidator.matches(pattern, candidate)) + + def test_matches_fail_for_different_entity_version(self): + pattern = UriSerializer.deserialize("//authority/A410/1/1003") + candidate = UriSerializer.deserialize("//authority/A410/3/1003") + self.assertFalse(UriValidator.matches(pattern, candidate)) + + def test_matches_fail_for_different_resource(self): + pattern = UriSerializer.deserialize("//authority/A410/3/ABCD") + candidate = UriSerializer.deserialize("//authority/A410/3/1003") + self.assertFalse(UriValidator.matches(pattern, candidate)) + if __name__ == "__main__": unittest.main() diff --git a/uprotocol/uri/factory/uri_factory.py b/uprotocol/uri/factory/uri_factory.py index ab3e8ad..51aee4e 100644 --- a/uprotocol/uri/factory/uri_factory.py +++ b/uprotocol/uri/factory/uri_factory.py @@ -26,11 +26,17 @@ class UriFactory: URI Factory that builds URIs from protos """ + WILDCARD_AUTHORITY = "*" + WILDCARD_ENTITY_ID = 0xFFFF + WILDCARD_ENTITY_VERSION = 0xFF + WILDCARD_RESOURCE_ID = 0xFFFF + + # URI that consists of wildcards only and therefore matches any URI. ANY = UUri( - authority_name="*", - ue_id=0xFFFF, - ue_version_major=0xFF, - resource_id=0xFFFF, + authority_name=WILDCARD_AUTHORITY, + ue_id=WILDCARD_ENTITY_ID, + ue_version_major=WILDCARD_ENTITY_VERSION, + resource_id=WILDCARD_RESOURCE_ID, ) @staticmethod diff --git a/uprotocol/uri/serializer/uriserializer.py b/uprotocol/uri/serializer/uriserializer.py index d0d749c..3c703ec 100644 --- a/uprotocol/uri/serializer/uriserializer.py +++ b/uprotocol/uri/serializer/uriserializer.py @@ -15,6 +15,7 @@ import re from typing import Optional +from uprotocol.uri.factory.uri_factory import UriFactory from uprotocol.uri.validator.urivalidator import UriValidator from uprotocol.v1.uri_pb2 import UUri @@ -26,9 +27,6 @@ class UriSerializer: serialization formats. """ - # The wildcard id for a field. - WILDCARD_ID = 0xFFFF - @staticmethod def serialize(uri: Optional[UUri]) -> str: """ @@ -125,11 +123,11 @@ def deserialize(uri: Optional[str]) -> UUri: return UUri() # Ensure that the major version is less than the wildcard - if new_uri.ue_version_major > UriValidator.MAJOR_VERSION_WILDCARD: + if new_uri.ue_version_major > UriFactory.WILDCARD_ENTITY_VERSION: return UUri() # Ensure that the resource id is less than the wildcard - if new_uri.resource_id > UriSerializer.WILDCARD_ID: + if new_uri.resource_id > UriFactory.WILDCARD_ENTITY_ID: return UUri() return new_uri diff --git a/uprotocol/uri/validator/urivalidator.py b/uprotocol/uri/validator/urivalidator.py index 95e4bb0..9d38333 100644 --- a/uprotocol/uri/validator/urivalidator.py +++ b/uprotocol/uri/validator/urivalidator.py @@ -14,6 +14,7 @@ from typing import Optional +from uprotocol.uri.factory.uri_factory import UriFactory from uprotocol.v1.uri_pb2 import UUri @@ -22,14 +23,8 @@ class UriValidator: Class for validating Uris. """ - # The minimum publish/notification topic id for a URI. - MIN_TOPIC_ID = 0x8000 - - # The Default resource id. - DEFAULT_RESOURCE_ID = 0 - - # The major version wildcard. - MAJOR_VERSION_WILDCARD = 0xFF + RESOURCE_ID_RESPONSE = 0 + RESOURCE_ID_MIN_EVENT = 0x8000 # The minimum event id for a URI. @staticmethod def is_empty(uri: UUri) -> bool: @@ -45,17 +40,14 @@ def is_empty(uri: UUri) -> bool: @staticmethod def is_rpc_method(uri: Optional[UUri]) -> bool: """ - Returns true if URI is of type RPC. A UUri is of type RPC if it - contains the word rpc in the resource name - and has an instance name and/or the id is less than MIN_TOPIC_ID. + Returns true if URI is of type RPC. A UUri is of type RPC if its + resource id is less than RESOURCE_ID_MIN_EVENT and greater than RESOURCE_ID_RESPONSE. @param uri: UUri to check if it is of type RPC @return: Returns true if this resource specifies an RPC method - call or RPC response. + call. """ return ( - uri is not None - and uri.resource_id != UriValidator.DEFAULT_RESOURCE_ID - and uri.resource_id < UriValidator.MIN_TOPIC_ID + uri is not None and UriValidator.RESOURCE_ID_RESPONSE < uri.resource_id < UriValidator.RESOURCE_ID_MIN_EVENT ) @staticmethod @@ -65,12 +57,19 @@ def is_rpc_response(uri: UUri) -> bool: """ return UriValidator.is_default_resource_id(uri) + @staticmethod + def is_notification_destination(uri: UUri) -> bool: + """ + @return Returns true if URI is of type RPC response. + """ + return UriValidator.is_default_resource_id(uri) + @staticmethod def is_default_resource_id(uri: UUri) -> bool: """ Returns true if URI is of type RPC response. """ - return not UriValidator.is_empty(uri) and uri.resource_id == UriValidator.DEFAULT_RESOURCE_ID + return not UriValidator.is_empty(uri) and uri.resource_id == UriValidator.RESOURCE_ID_RESPONSE @staticmethod def is_topic(uri: UUri) -> bool: @@ -80,4 +79,123 @@ def is_topic(uri: UUri) -> bool: @param uri {@link UUri} to check if it is of type Topic @return Returns true if URI is of type Topic. """ - return not UriValidator.is_empty(uri) and uri.resource_id >= UriValidator.MIN_TOPIC_ID + return not UriValidator.is_empty(uri) and uri.resource_id >= UriValidator.RESOURCE_ID_MIN_EVENT + + @staticmethod + def matches_authority(uri_to_match: UUri, candidate_uri: UUri) -> bool: + """ + Checks if the authority of the uri_to_match matches the candidate_uri. + A match occurs if the authority name in uri_to_match is a wildcard + or if both URIs have the same authority name. + + :param uri_to_match: The URI to match. + :param candidate_uri: The candidate URI to match against. + :return: True if the authority names match, False otherwise. + """ + return ( + uri_to_match.authority_name == UriFactory.WILDCARD_AUTHORITY + or uri_to_match.authority_name == candidate_uri.authority_name + ) + + @staticmethod + def matches_entity_id(uri_to_match: UUri, candidate_uri: UUri) -> bool: + """ + Checks if the entity ID of the uri_to_match matches the candidate_uri. + A match occurs if the entity ID in uri_to_match is a wildcard (0xFFFF) + or if the masked entity IDs of both URIs are equal. + + The entity ID masking is performed using a bitwise AND operation with + 0xFFFF. If the result of the bitwise AND operation between the + uri_to_match's entity ID and 0xFFFF is 0xFFFF, it indicates that the + uri_to_match's entity ID is a wildcard and can match any entity ID. + Otherwise, the function checks if the masked entity IDs of both URIs + are equal, meaning that the relevant parts of their entity IDs match. + + :param uri_to_match: The URI to match. + :param candidate_uri: The candidate URI to match against. + :return: True if the entity IDs match, False otherwise. + """ + return (uri_to_match.ue_id & UriFactory.WILDCARD_ENTITY_ID) == UriFactory.WILDCARD_ENTITY_ID or ( + uri_to_match.ue_id & UriFactory.WILDCARD_ENTITY_ID + ) == (candidate_uri.ue_id & UriFactory.WILDCARD_ENTITY_ID) + + @staticmethod + def matches_entity_instance(uri_to_match: UUri, candidate_uri: UUri) -> bool: + """ + Checks if the entity instance of the uri_to_match matches the candidate_uri. + A match occurs if the upper 16 bits of the entity ID in uri_to_match are zero + or if the upper 16 bits of the entity IDs of both URIs are equal. + + :param uri_to_match: The URI to match. + :param candidate_uri: The candidate URI to match against. + :return: True if the entity instances match, False otherwise. + """ + return (uri_to_match.ue_id & 0xFFFF_0000) == 0x0000_0000 or (uri_to_match.ue_id & 0xFFFF_0000) == ( + candidate_uri.ue_id & 0xFFFF_0000 + ) + + @staticmethod + def matches_entity_version(uri_to_match: UUri, candidate_uri: UUri) -> bool: + """ + Checks if the entity version of the uri_to_match matches the candidate_uri. + A match occurs if the entity version in uri_to_match is a wildcard + or if both URIs have the same entity version. + + :param uri_to_match: The URI to match. + :param candidate_uri: The candidate URI to match against. + :return: True if the entity versions match, False otherwise. + """ + return ( + uri_to_match.ue_version_major == UriFactory.WILDCARD_ENTITY_VERSION + or uri_to_match.ue_version_major == candidate_uri.ue_version_major + ) + + @staticmethod + def matches_entity(uri_to_match: UUri, candidate_uri: UUri) -> bool: + """ + Checks if the entity of the uri_to_match matches the candidate_uri. + A match occurs if the entity ID, entity instance, and entity version + of both URIs match according to their respective rules. + + :param uri_to_match: The URI to match. + :param candidate_uri: The candidate URI to match against. + :return: True if the entities match, False otherwise. + """ + return ( + UriValidator.matches_entity_id(uri_to_match, candidate_uri) + and UriValidator.matches_entity_instance(uri_to_match, candidate_uri) + and UriValidator.matches_entity_version(uri_to_match, candidate_uri) + ) + + @staticmethod + def matches_resource(uri_to_match: UUri, candidate_uri: UUri) -> bool: + """ + Checks if the resource of the uri_to_match matches the candidate_uri. + A match occurs if the resource ID in uri_to_match is a wildcard + or if both URIs have the same resource ID. + + :param uri_to_match: The URI to match. + :param candidate_uri: The candidate URI to match against. + :return: True if the resource IDs match, False otherwise. + """ + return ( + uri_to_match.resource_id == UriFactory.WILDCARD_RESOURCE_ID + or uri_to_match.resource_id == candidate_uri.resource_id + ) + + @staticmethod + def matches(uri_to_match: UUri, candidate_uri: UUri) -> bool: + """ + Checks if the entire URI (authority, entity, and resource) of the uri_to_match + matches the candidate_uri. A match occurs if the authority, entity, and resource + of both URIs match according to their respective rules. + + :param uri_to_match: The URI to match. + :param candidate_uri: The candidate URI to match against. + :return: True if the entire URIs match, False otherwise. + """ + return ( + UriValidator.matches_authority(uri_to_match, candidate_uri) + and UriValidator.matches_entity(uri_to_match, candidate_uri) + and UriValidator.matches_resource(uri_to_match, candidate_uri) + )