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 (uE). 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 Client 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.
A transport invokes this method for each newly arrived message that matches the criteria specified as part of registering 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.
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
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 |
UMessage |
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 with a
UCode.UNIMPLEMENTED
if the transport does not support the pull delivery method -
MUST fail with a
UCode.NOT_FOUND
if there are no matching messages 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. After this method has completed successfully, the given listener will be invoked for each message that matches the given source and sink filter patterns according to the rules defined by the UUri specification.
UTransport implementations
-
MUST fail with a
UCode.UNIMPLEMENTED
if the transport does not support the push delivery method. In that case, the unregisterListener method MUST also fail accordingly. -
MUST fail with a
UCode.RESOURCE_EXHAUSTED
, if the maximum number of listeners is reached -
MUST support registering more than one listener for any given address patterns
-
MUST support registering the same listener for multiple address patterns
-
MUST document the maximum supported number of listeners per address pattern.
-
MUST be idempotent, multiple calls to said API with the same parameters MUST have the same effect as a single call.
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 max listeners exceeded
T--)C : error : UStatus(UCode.RESOURCE_EXHAUSTED)
else other
T--)C : error : UStatus
end
end
deactivate T
In RegisterListener API, we have the source and sink UUri
in the arguments.
However, not all the combinations of source and sink UUri
are valid.
The following table are the valid combinations, which maps to the corresponding user case.
source resource_id |
sink resource_id |
Publish | Notification | Request | Response | Use Case |
---|---|---|---|---|---|---|
[8000-FFFE] |
None |
V |
A uE listens for a specific published message |
|||
[8000-FFFE] |
0 |
V |
A uE listens for a specific notification message |
|||
0 |
[1-7FFF] |
V |
A uE listens for a specific request |
|||
[1-7FFF] |
0 |
V |
A uE listens for a specific response |
|||
FFFF |
0 |
V |
V |
A uE listens for all notifications and responses |
||
FFFF |
FFFF |
V |
V |
V |
uStreamer listens for all notifications, requests, and responses |
Note that other combinations not in the table MUST never be used in RegisterListener API.
Sometimes it’s necessary to distinguish the message types which should be listened to. We can get the message types by reorganizing the table above to the following one.
Message Type | Possible resource_id combinations {src_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. After this method has returned successfully, the listener will no longer be invoked for any (matching) messages.
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 with a
UCode.UNIMPLEMENTED
if the transport does not support the push Message Delivery. In that case, the RegisterListener method MUST also fail accordingly. -
MUST fail with a
UCode.NOT_FOUND
, if no such listener had been registered before
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 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 can might be useful for normalizing local URIs passed into the Transport Layer API methods with authority information.
A uEntity invokes this method to get its own authority.
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 and 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 and 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.
Transport API implementations
-
MUST support at least one of push or pull delivery methods and MAY support both
-
MUST document the delivery methods they support
Communication protocols like MQTT, 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 defines bindings to the following communication protocols:
Each uProtocol Client MUST employ exactly one of these bindings for implementing the Transport Layer API.
Additional bindings MAY be defined in future versions of uProtocol.
A binding MAY employ CloudEvents as a means to map uProtocol messages to the communication protocol’s PDU. In order to provide for consistency across implementations, such bindings MUST adhere to UMessage mapping to CloudEvents