The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "MAY", and "OPTIONAL" in this document are to be interpreted as described in IETF BCP14 (RFC2119 & RFC8174)
SPDX-FileCopyrightText: 2023 Contributors to the Eclipse Foundation See the NOTICE file(s) distributed with this work for additional information regarding copyright ownership. This program and the accompanying materials are made available under the terms of the Apache License Version 2.0 which is available at https://www.apache.org/licenses/LICENSE-2.0 SPDX-FileType: DOCUMENTATION SPDX-License-Identifier: Apache-2.0
uProtocol uses Uniform Resource Identifiers (URI) to uniquely identify (and/or address) resources such as devices, software components, topics, methods, etc on a computer network. RFC3986 defines the structure of a URI as follows:
Using this terminology, a uProtocol URI (UUri) can be structured like this:
A UUri can be represented as:
-
An instantiation of the Data Model in a supported programming language
-
A URI string as defined by Mapping to URI
In the remainder of this document, the term UUri is used to refer to an instantiation of the data model, whereas the term URI is used to refer to its textual representation.
The diagram below shows a UUri and its properties using UML2 notation.
classDiagram
class UUri {
authority_name : String
ue_id : UInt32
ue_version_major : UInt8
resource_id : UInt16
}
Each uProtocol Language Library MUST implement the data model using the type and property names as defined in the following sections.
Each uProtocol Language Library MUST support writing and reading of an instance of the data model to/from a protobuf as defined in uri.proto.
The remainder of this section defines the requirements for the data model by means of invariants which must hold true at all times. Additionally, there are some predicates that can be used by uEntities to determine if a UUri contains the information they are interested in. Both the invariants and the predicates are (formally) defined by means of Object Constraint Language (OCL) expressions on the UUri data model. The OCL Tutorial provides an overview of the basic concepts used in the definitions.
An authority represents the deployment location of a specific software entity. The location is represented by means of a logical identifier like a domain name (e.g. mcu1.example.com
) or a Vehicle Identification Number (VIN).
A UUri’s authority_name
MUST NOT exceed 128 characters in length.
inv: authority_name.length() <= 128
A uProtocol software entity (uEntity) is a piece of software deployed somewhere on a network host. uEntities are uniquely identified within a system by means of the type and version of the service interface that they implement and an instance identifier in case multiple instances of the same service are deployed in the system.
Note
|
uEntities which produce events for consumption by other uEntities assume a Service role. uEntities which consume events produced by other uEntities assume an Application role. uEntities MAY assume both the Service and Application roles. |
A UUri’s ue_id
property value determines the type and instance of the service being referred to:
-
The value’s least significant 16 bits contain the service ID, representing the service interface type.
-
The value’s most significant 16 bits contain the service instance ID.
A service interface consists of resources and methods. A resource usually represents the state of a property of the service (instance) while methods are used to manage the state of a service (instance).
Resources and methods are uniquely identified within a service interface by means of their numeric identifier.
A UUri’s resource_id
contains the identifier of the resource or method being referred to.
It is often helpful to represent UUris in a textual format, for example when serializing uProtocol messages to a transport’s Protocol Data Unit (PDU). This section defines how UUris can be mapped to and from a URI according to the URI-Reference rule of RFC3986.
The URIs mapped from UUris use the syntax defined in RFC3986, Appendix A with a few modifications:
A URI MUST NOT use any scheme other than up
and MUST NOT have a query component and MUST NOT have a fragment component.
URI = "up" ":" hier-part
relative-ref = relative-part
The authority component of a URI MUST consist of characters according to the following rule:
lc-unreserved = *( %x61-7A / DIGIT / "-" / "." / "_" / "~" )
; lowercase only unreserved
authority = IP-literal / IPv4address / lc-unreserved / "*"
In particular, the authority MUST NOT contain any userinfo nor port.
Note
|
The ABNF fragments above only contain the rules that differ from the original definitions in RFC3986. |
A URI’s authority MUST be mapped to/from the UUri’s authority_name
property following the rules defined in RFC3986, Section 3.2.2. In particular, the URI MUST NOT contain an authority if authority_name
is empty and vice versa.
A URI’s path MUST be mapped to/from the UUri’s ue_id
, ue_version_major
and resource_id
properties.
Each property value MUST be mapped to a segment following the rules defined in RFC3986, Section 3.3.
The ue_id
, ue_version_major
and resource_id
MUST be mapped to the upper-case base16 encoding
of the corresponding property values. Leading zeros (0
) MAY be omitted.
Each uProtocol Language Library MUST provide means to serialize UUris to the URI format and vice versa. A concrete implementation should follow common practices for the particular programming language.
For example, a Java library might implement a UriSerializer
class providing corresponding static methods.
public final class UriSerializer {
/**
* @returns The UUri parsed from the given string representation.
* @throws UuriSerializationException if the given string is not a valid URI.
The exception may contain details regarding the violated
constraint(s).
*/
public static UUri deserialize(String: uri) throws UuriSerializationException {
...
}
/**
* @returns The given UUri's string representation.
* @throws UuriSerializationException if the UUri cannot be serialized.
*/
public static String serialize(UUri: uuri) throws UuriSerializationException {
...
}
}
Alternatively, the UUri
class might provide corresponding methods.
public class UUri {
/**
* @returns The UUri parsed from the given string representation.
* @throws UuriSerializationException if the given string is not a valid
URI. The exception may contain details
* regarding the violated constraint(s).
*/
public static UUri fromUri(String: uri) throws UuriSerializationException {
...
}
/**
* @returns The given UUri's string representation.
* @throws UuriSerializationException if this UUri cannot be serialized.
*/
public final String toUri() throws UuriSerializationException {
...
}
}
Similarly, a Rust library might implement a UriSerializer
struct providing corresponding functions
pub struct UriSerializer {}
impl UriSerializer {
pub fn try_deserialize(uri: &str) -> Result<UUri, UuriSerializationError> {
...
}
pub fn try_serialize(uuri: &UUri) -> Result<String, UuriSerializationError> {
...
}
}
or implement the functions on the UUri
struct
impl UUri {
pub fn try_from_uri(uri: &str) -> Result<UUri, UuriSerializationError> {
...
}
pub fn try_to_uri(&self) -> Result<String, UuriSerializationError> {
...
}
}
A UUri can be used to define a pattern that other UUris can then be matched against. For that purpose, a UUri
-
MAY have its
authority_name
set to the*
(U+002A
, Asterisk) character in order to match any (including no) authority. -
MAY have the service ID part of its
ue_id
set to0xFFFF
in order to match any service type. -
MAY have the service instance ID part of its
ue_id
set to0xFFFF
in order to match any service instance.0x0000
is the default instance ID used when there is only a single instance of a service. -
MAY have its
ue_version_major
set to0xFF
in order to match any version. -
MAY have its
resource_id
set to0xFFFF
in order to match any resource.
A candidate UUri matches a particular pattern UUri if all of the candidate UUri’s properties match the pattern UUri’s corresponding properties according to the rules defined by the predicates below.
context (pattern) UUri
def: matches_authority(candidate : UUri) : Boolean =
self.authority_name = '*'
or
self.authority_name = candidate.authority_name
def: service_type() : UInt32 = ue_id & 0x0000_FFFF
def: service_instance() : UInt32 = ue_id & 0xFFFF_0000
def: matches_entity_type(candidate : UUri) : Boolean =
self.service_type() = 0x0000_FFFF
or
self.service_type() = candidate.service_type()
def: matches_entity_instance(candidate : UUri) : Boolean =
self.service_instance() = 0xFFFF_0000
or
self.service_instance() = candidate.service_instance()
def: matches_entity_version(candidate : UUri) : Boolean =
self.ue_version_major = 0xFF
or
self.ue_version_major = candidate.ue_version_major
def: matches_resource(candidate : UUri) : Boolean =
self.resource_id = 0xFFFF
or
self.resource_id = candidate.resource_id
def: matches(candidate : UUri) : Boolean =
self.matches_authority(candidate)
and
self.matches_entity_type(candidate)
and
self.matches_entity_instance(candidate)
and
self.matches_entity_version(candidate)
and
self.matches_resource(candidate)
Each uProtocol Language Library MUST provide means to perform UUri pattern matching according to the matches
predicate as defined above.
The following pattern UUri
UUri { authority_name: "192.168.1.100", ue_id: 0xFFFF_FFFF // any instance, any service ue_version_major: 0xFF, // any resource_id: 0xFFFF // any }
will match the following URIs:
//192.168.1.100/0/3/8000 //192.168.1.100/1/3/8 //192.168.1.100/1A/2/2
But not these:
//192.168.1.200/0/3/8000 // wrong authority /1/3/8 // no authority
The following pattern UUri
UUri { authority_name: "*", // any ue_id: 0xFFFF_0000, // any instance of service 0x0000 ue_version_major: 0x03, resource_id: 0xFFFF // any }
will match the following URIs:
//other-vcu.my-vehicle/0/3/8000 /20000/3/2
But not these:
//vcu.other.device/1/3/8000 // wrong service ID (0x0001) /20010/3/2 // wrong service ID (0x0010)
The following pattern UUri
UUri { authority_name: "", // local ue_id: 0x0000_0000, // default instance of service 0x0000 ue_version_major: 0xFF, // any resource_id: 0x0001 }
will match the following URIs:
/0/3/1 /0/2/1
But not these:
//vcu.other.device/0/3/1 // non-local authority /0/3/3 // wrong resource
The numerical identifiers of a uService’s type and its resources are defined in the service’s proto3 definition by means of corresponding Protobuf Options.
Applications can determine these identifiers during runtime from the client stubs generated from a uService proto3 file via the corresponding MessageDescriptors.