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
The Transport & Session Layer is responsible for bidirectional point-2-point communication between uEntities (occasionally abbreviated as uE, especially in diagrams).
The purpose of this layer of uProtocol is to define a common API for sending and receiving messages across different transport protocols like Eclipse Zenoh, MQTT 5 or Android Binder, runtime environments like Android, Linux or MCUs, and programming languages like Java, Rust, Python or C/C++.
This specification defines the transport layer’s abstract API which is mapped to supported programming languages by means of uProtocol’s language specific libraries. The API is implemented for particular transport protocols and programming languages by uProtocol Transport Libraries.
The Transport Layer API is defined using UML2 notation.
classDiagram
class UTransport {
<<interface>>
send(message : UMessage)
receive(sourceFilter: UUri, sinkFilter: UUri [0..1]) UMessage
registerListener(sourceFilter: UUri, sinkFilter: UUri [0..1], listener: UListener)
unregisterListener(sourceFilter: UUri, sinkFilter: UUri [0..1], listener: UListener)
}
class UListener {
<<interface>>
onReceive(message : UMessage)
}
class LocalUriProvider {
<<interface>>
getAuthority() String
getSource() UUri
getResource(id: UInt16) UUri
}
UTransport ..> UListener
|
Note
|
The data types used in the following sections are defined in uProtocol Basic Types. |
A uEntity registers a UListener with the transport in order to process (incoming) messages that are of interest to the uEntity.
Each language library MUST declare the UListener interface using corresponding programming language specific means.
A UTransport implementation invokes this method for each newly arrived message that matches the criteria specified during registration of the listener.
onReceive(message: UMessage)| Parameter | Type | Description |
|---|---|---|
message |
The newly received message. |
sequenceDiagram
participant L as listener:UListener
participant T as transport:UTransport
T-)L : onReceive(UMessage)
activate L
deactivate L
This is the main entry point into a transport’s messaging functionality.
Each language library MUST declare the UTransport interface using corresponding programming language specific means.
Clients use this method to transmit a single message.
send(message: UMessage)| Parameter | Type | Description |
|---|---|---|
message |
The message to send. |
The successful completion of this method means that the given message has been handed over to the underyling communication protocol’s message delivery mechanism. For a hub-and-spoke based communication protocol like MQTT, this typically means that the send method implementation has received the MQTT PUBACK packet, which indicates that the message has been transferred successfully to the broker from where the message remains to be retrieved by potential recipients. For a peer-to-peer based protocol like HTTP, this typically means that the send method implementation has received the peer’s HTTP response message, which indicates that the message has been transferred successfully to the peer’s HTTP endpoint. Based on that, a client using this method should not assume that the given message has already reached its destination nor that it has already been processed once the method has completed successfully.
On the other hand, unsuccessful completion of this method does not necessarily mean that the given message has not been sent at all. For example, an MQTT based implementation might lose its connection to the MQTT broker after it has sent its MQTT PUBLISH packet but before it has received the broker’s PUBACK. In such a case, clients should use the returned error to determine, if another attempt to send the message is feasible or not. For example, if the initial attempt to send the message has failed with a UCode.INVALID_ARGUMENT, then trying to send the same unaltered message again will most likely yield the same result. However, if the initial attempt failed with a UCode.UNAVAILABLE, then resending the message using some back-off mechanism will likely succeed eventually.
|
Note
|
The above strategy for retrying failed attempts to send a message results in at-least-once delivery. Recipient(s) of these messages should therefore be Idempotent Processors. |
UTransport implementations
-
MUST preserve all of the message’s meta data and payload during transmission
-
MUST fail invocations of Send with a
UCode.INVALID_ARGUMENT, if the passed UMessage failed validation.
-
MUST provide means to prevent consumers from processing of messages with a source address that does not match the identity of the uEntity that the message being sent originates from.
In general, an implementation of this specification will run in the same process as the uEntity code and thus has no way of objectively asserting the uEntity’s authorities on its own. However, most transports will support or require clients to provide credentials for authentication as part of establishing a connection or sending messages. In most of these cases, the messaging infrastructure (like MQTT brokers or Eclipse Zenoh) also support the definition of authenticated clients' authorities by means of Access Control Lists (ACLs). These mechanisms MAY then be used to implement this requirement.
Implementations SHOULD fail invocations of Send with a
UCode.PERMISSION_DENIED, if the message can be determined to be in violation of any of the uEntity’s configured authorities at the time of sending. Otherwise, implementations MUST succeed the invocation and MAY rely on the messaging infrastructure to (silently) ignore the message.NoteCertain uEntities like the uStreamer component are actually intended to consume and forward messages that originate from other uEntities. However, even in this case a mechanism as described above may be used to restrict the uStreamer’s ability to forward only those messages that match its defined forwarding rules.
-
MUST document if and how the implementation maps the UMessage Priority Classes to an existing corresponding mechanism for message prioritization of the underlying transport protocol.
NoteAn implementation MAY also handle all messages equally, regardless of priority class.
sequenceDiagram
actor C as Client
participant T as transport:UTransport
C->>T : send(UMessage)
activate T
opt error while sending
Note right of T: message may or may<br>not have been sent
T--)C : error : Ustatus
end
deactivate T
Clients use this method to receive a single message matching given filter criteria.
receive(sourceFilter: UUri, sinkFilter: UUri [0..1]) : UMessage| Parameter | Type | Description |
|---|---|---|
sourceFilter |
The source address pattern that messages need to match. |
|
sinkFilter |
The sink address pattern that messages need to match. If omitted, a message MUST NOT contain any sink address in order to match. |
|
result |
The least recent message that matches the given filter criteria and has not expired yet. |
This method implements the pull delivery method on top of the underlying communication protocol.
UTransport implementations
-
MUST fail invocations of Receive with a
UCode.UNIMPLEMENTED, if the transport does not support the pull delivery method.
-
MUST fail invocations of Receive with a
UCode.NOT_FOUND, if there are no matching messages available. This is also the case if the a package data unit cannot be deserialized into a valid uProtocol message. Transport implementations SHOULD use the validation functionality provided by language libraries for this purpose, if available.
sequenceDiagram
actor C as Client
participant T as transport:UTransport
C->>T : receive(UUri, UUri)
activate T
alt pull not supported
T--)C : error : UStatus(UCode.UNIMPLEMENTED)
else no message available
T--)C : error : UStatus(UCode.NOT_FOUND)
else
T--)C : matching message : UMessage
end
deactivate T
Clients use this method to register a listener for messages matching given filter criteria.
registerListener(sourceFilter: UUri, sinkFilter: UUri [0..1], listener: UListener)| Parameter | Type | Description |
|---|---|---|
sourceFilter |
The source address pattern that messages need to match. |
|
sinkFilter |
The sink address pattern that messages need to match. If omitted, a message must not contain any sink address in order to match. |
|
listener |
The listener to be registered. |
This API is used to implement the push delivery method on top of the underlying communication protocol.
UTransport implementations
-
MUST fail invocations of RegisterListener with a
UCode.UNIMPLEMENTEDif the transport does not support the push delivery method. In that case, the unregisterListener method MUST also fail accordingly.
-
MUST provide means to configure upper limits for
-
the overall number of listeners that can be registered and
-
the number of listeners that can be registered per address filter pattern.
-
-
SHOULD use a reasonable default value for both limits.
-
MUST fail invocations of RegisterListener with a
UCode.RESOURCE_EXHAUSTED, if the maximum number of listeners supported by the transport has already been registered.
-
MUST fail invocations of RegisterListener with a
UCode.INVALID_ARGUMENT, if the source and sink filter arguments' resource IDs do not match any of the entries from the table below:Table 5. Valid Source/Sink Resource IDs Use Case source resource_idsink resource_idListen for events published to a particular topic
[8000-FFFE]
None
Listen for events published to any topic
FFFF
None
Listen for notifications from a specific resource
[8000-FFFE]
0
Listen for RPC requests to a specific operation
0
[1-7FFF]
Listen for RPC responses from a specific operation
[1-7FFF]
0
Listen for all incoming notifications and responses
FFFF
0
Listen for all incoming notifications, RPC requests, and responses
FFFF
FFFF
-
MUST support registering more than one listener for any given address filter patterns
-
MUST support registering the same listener for multiple address filter patterns
-
MUST make sure that multiple calls to RegisterListener with the same parameters have the same effect as a single call.
-
MUST deliver matching messages to a successfully registered listener. This means that for each message that the transport receives after RegisterListener has completed successfully, and which matches the listener’s source and sink filter criteria according to the UUri pattern matching rules, the transport MUST invoke the listener’s OnReceive method at least once.
-
MUST discard any inbound transport specific package data unit that cannot be deserialized into a valid uProtocol message. Transport implementations SHOULD use the validation functionality provided by language libraries for this purpose, if available.
-
MUST provide means to prevent a uEntity using this transport from consuming messages that it is not authorized to process.
In general, an implementation of this specification will run in the same process as the uEntity code and thus has no way of objectively asserting the uEntity’s authorities on its own. However, most transports will support or require clients to provide credentials for authentication as part of establishing a connection or sending messages. In most of these cases, the messaging infrastructure (like MQTT brokers or Eclipse Zenoh) also support the definition of authenticated clients' authorities by means of Access Control Lists (ACLs). These mechanisms MAY then be used to implement this requirement.
Implementations SHOULD fail invocations of RegisterListener with a
UCode.PERMISSION_DENIED, if the source and/or sink filters can be determined to be in violation of any of the uEntity’s configured authorities at the time of registering the listener. Otherwise, implementations MUST succeed the invocation and MAY rely on the messaging infrastructure to (silently) ignore any messages that the uEntity is not authorized to process.NoteCertain uEntities like the uStreamer component are actually intended to consume and forward messages that originate from other uEntities. However, even in this case a mechanism as described above may be used to restrict the uStreamer’s ability to forward only those messages that match its defined forwarding rules.
sequenceDiagram
actor C as Client
participant T as transport:UTransport
C->>T : register(UUri, UUri, UListener)
activate T
opt error
alt push not supported
T--)C : error : UStatus(UCode.UNIMPLEMENTED)
else invalid filter syntax
T--)C : error : UStatus(UCode.INVALID_ARGUMENT)
else max listeners exceeded
T--)C : error : UStatus(UCode.RESOURCE_EXHAUSTED)
else other
T--)C : error : UStatus
end
end
deactivate T
Sometimes it’s necessary to distinguish the message types which should be listened to.
| Message Type | Possible resource ID combinations {source resource_id, sink resource_id} |
|---|---|
Publish |
{[8000-FFFE], None} |
Notification |
{[8000-FFFE], 0}, {FFFF, 0}, {FFFF, FFFF} |
Request |
{0, [1-7FFF]}, {FFFF, FFFF} |
Response |
{[1-7FFF], 0}, (FFFF, 0), {FFFF, FFFF} |
Clients use this method to unregister a previously registered listener.
unregisterListener(sourceFilter: UUri, sinkFilter: UUri [0..1], listener: UListener)| Parameter | Type | Description |
|---|---|---|
sourceFilter |
The source address pattern that the listener had been registered for. |
|
sinkFilter |
The sink address pattern that the listener had been registered for. |
|
listener |
The listener to be unregistered. |
UTransport implementations
-
MUST fail invocations of UnregisterListener with a
UCode.UNIMPLEMENTED, if the transport does not support the push Message Delivery Methods. In that case, the RegisterListener method MUST also fail accordingly.
-
MUST fail invocations of UnregisterListener with a
UCode.NOT_FOUND, if no such listener had been registered before.
-
MUST fail invocations of UnregisterListener with a
UCode.INVALID_ARGUMENT, if the source and sink filter arguments' resource IDs do not match any of entries from Valid Source/Sink Resource IDs.
-
MUST NOT deliver any messages to a successfully unregistered listener.
sequenceDiagram
actor C as Client
participant T as transport:UTransport
C->>T : unregister(UUri, UUri, UListener)
activate T
opt error
alt push not supported
T--)C : error : UStatus(UCode.UNIMPLEMENTED)
else no such listener
T--)C : error : UStatus(UCode.NOT_FOUND)
else invalid filter syntax
T--)C : error : UStatus(UCode.INVALID_ARGUMENT)
else other
T--)C : error : UStatus
end
end
deactivate T
A uEntity can use the LocalUriProvider to create URIs representing the uEntity’s local resources during runtime. This information can then be used in messages to be sent to other uEntities.
A UTransport implementation can use the LocalUriProvider to determine the uEntity’s authority during runtime. This information might be useful for normalizing local URIs passed into the Transport Layer API methods which do not contain an authority name.
Each language library MUST declare the LocalUriProvider interface using corresponding programming language specific means.
A uEntity invokes this method to get its own authority.
The value returned by an implementation MUST be the uEntity’s (fixed) local authority name. Implementations MAY use any appropriate mechanism to determine the local authority during runtime, e.g. by means of a configuration file, environment variables or a central registry.
A uEntity invokes this method to get the address that it expects incoming Notification or RPC Response messages to be sent to.
The address returned by an implementation MUST consist of the uEntity’s (fixed) authority, identifier, major version and resource ID 0x0000.
Implementations MAY use any appropriate mechanism to determine these values during runtime, e.g. by means of a configuration file, environment variables or a central registry.
A uEntity invokes this method to get a resource specific address to publish messages to or that it expects incoming RPC Request messages to be sent to.
The address returned by an implementation MUST consist of the uEntity’s (fixed) authority, identifier, major version and the passed in resource ID. Implementations MAY use any appropriate mechanism to determine these values during runtime, e.g. by means of a configuration file, environment variables or a central registry.
uProtocol distinguishes between the following message delivery methods:
| Method | Definition |
|---|---|
Data is pushed by the sender to the receiver. |
|
Data is pulled in by the receiver. This is done periodically at certain intervals, because the receiver (usually) does not know when new data will be available. |
UTransport implementations
-
MUST support at least one of push or pull delivery methods and MAY support both.
Communication protocols like MQTT or HTTP define a specific Protocol Data Unit (PDU) for conveying control information and user data. A uProtocol Client implements the Transport Layer API defined above on top of such a communication protocol.
A communication protocol binding defines how the uProtocol Transport Layer API maps to the communication protocol’s message exchange pattern(s) and how uProtocol messages are mapped to the protocol’s PDU. Many communication protocols distinguish between a message’s metadata and the (raw) payload. This is often reflected by the structure of the protocol’s PDU. For example, HTTP supports header fields and a body which can be used to convey a uProtocol message’s attributes and payload respectively.
uProtocol currently defines bindings to the following communication protocols: