Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
77 changes: 74 additions & 3 deletions tests/test_uri/test_validator/test_urivalidator.py
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand All @@ -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):
Expand All @@ -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()
14 changes: 10 additions & 4 deletions uprotocol/uri/factory/uri_factory.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
8 changes: 3 additions & 5 deletions uprotocol/uri/serializer/uriserializer.py
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand All @@ -26,9 +27,6 @@ class UriSerializer:
serialization formats.
"""

# The wildcard id for a field.
WILDCARD_ID = 0xFFFF

@staticmethod
def serialize(uri: Optional[UUri]) -> str:
"""
Expand Down Expand Up @@ -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
152 changes: 135 additions & 17 deletions uprotocol/uri/validator/urivalidator.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@

from typing import Optional

from uprotocol.uri.factory.uri_factory import UriFactory
from uprotocol.v1.uri_pb2 import UUri


Expand All @@ -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:
Expand All @@ -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
Expand All @@ -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:
Expand All @@ -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)
)