proxystore.endpoint.protocol¶
Wire protocol of endpoints.
Clients communicate with their local endpoint over a TCP connection using
length-prefixed binary messages, and endpoints send the same messages to
their peers over iroh streams. This module only contains the encoding and
decoding logic. The
MessageReader
parses messages without performing I/O so the same parser is used by the
blocking client, the asyncio server, and the iroh streams of peers.
A connection starts with a handshake:
- The client sends a preamble (
MAGICand the newest protocol version it supports) followed by aHELLOmessage with a random nonce and the client's versions. - The endpoint replies with a preamble containing the negotiated protocol
version (see
negotiate_version()) and a message with its own nonce and a proof that it knows the endpoint's token. - The client verifies the endpoint's proof then sends an
AUTHmessage with its own proof. - The endpoint verifies the client's proof and replies with its information (ID, name, and versions).
The formats of the preamble and the
Hello message must never change
(fields may only be added to Hello) because they are sent before the
version is negotiated. If the client and endpoint support no common version,
the endpoint replies with only its preamble, containing the newest version it
supports, and closes the connection.
After the handshake, each request and response is a message consisting of a
fixed-size Header, JSON-encoded
metadata (e.g., the key), and a raw data payload. The endpoint echoes the
request ID of each request in its response.
Peers do not use the handshake because iroh authenticates each endpoint.
Each request to a peer is sent on its own stream so the request ID is
unused. Peers exchange the same messages as clients and endpoints, and the
protocol version is negotiated as part of the ALPN of the connection (see
supported_alpns()).
PROTOCOL_VERSION
module-attribute
¶
Newest version of the protocol supported by this version of ProxyStore.
Increment on any incompatible change to the handshake or message formats.
MIN_PROTOCOL_VERSION
module-attribute
¶
Oldest version of the protocol supported by this version of ProxyStore.
Clients and endpoints use the newest version both support, so keeping support for older versions lets clients and endpoints be upgraded separately. Only increase this when dropping support for an old version.
NONCE_SIZE
module-attribute
¶
Size in bytes of the random nonces exchanged in the handshake.
MAX_META_SIZE
module-attribute
¶
Maximum size in bytes of the metadata in a message.
VERSION_DOCS_URL
module-attribute
¶
Documentation on version compatibility between clients and endpoints.
MessageData
module-attribute
¶
Data of a message.
Unlike BytesLike, views are excluded
because endpoints store the data after handling the message.
STATUS_ERRORS
module-attribute
¶
STATUS_ERRORS: dict[Status, type[EndpointError]] = {
Status.ERROR: EndpointRequestError,
Status.UNAUTHORIZED: EndpointAuthError,
Status.BAD_REQUEST: EndpointProtocolError,
Status.TOO_LARGE: ObjectSizeExceededError,
Status.PEERING_DISABLED: PeeringDisabledError,
Status.PEER_NOT_ALLOWED: PeerNotAllowedError,
Status.PEER_UNAVAILABLE: PeerUnavailableError,
Status.STORAGE_FULL: StorageFullError,
}
Exception type corresponding to each error status.
Nonce
module-attribute
¶
Nonce = Annotated[
bytes,
Field(min_length=NONCE_SIZE, max_length=NONCE_SIZE),
]
Random nonce exchanged in the handshake.
Op
¶
Status
¶
Bases: IntEnum
Status codes of messages sent by an endpoint.
The metadata of a response with an error status contains the error
message. Each error status corresponds to one exception type (see
raise_for_status()).
Statuses added in later versions of the protocol must be errors so that older clients can handle them as generic errors.
TOO_LARGE
class-attribute
instance-attribute
¶
Request data exceeds the maximum object size of the endpoint.
PEERING_DISABLED
class-attribute
instance-attribute
¶
Request targets a peer but peering is disabled on the endpoint.
PEER_NOT_ALLOWED
class-attribute
instance-attribute
¶
Request targets a peer which is not allowed to communicate.
PEER_UNAVAILABLE
class-attribute
instance-attribute
¶
Request targets a peer which cannot be reached.
STORAGE_FULL
class-attribute
instance-attribute
¶
Request data does not fit in the storage of the endpoint.
Preamble
dataclass
¶
Preamble(version: int = PROTOCOL_VERSION)
Preamble that starts every connection.
The format of the preamble must never change so that clients and endpoints using different protocol versions can detect the mismatch.
Attributes:
-
version(int) –Protocol version of the sender.
FORMAT
class-attribute
¶
Format of the magic bytes and protocol version.
unpack
classmethod
¶
Unpack a preamble.
Raises:
-
EndpointProtocolError–If the preamble does not start with
MAGIC.
Source code in proxystore/endpoint/protocol.py
Header
dataclass
¶
Message header.
Attributes:
-
code(int) – -
flags(int) –Reserved for future use.
-
request_id(int) –ID chosen by the client for a request and echoed in the response. IDs allow requests to be pipelined in the future. Handshake messages and messages between peers use
0. -
meta_len(int) –Length in bytes of the metadata.
-
data_len(int) –Length in bytes of the data.
FORMAT
class-attribute
¶
Format of the code, flags, request ID, metadata length, and data length.
unpack
classmethod
¶
Unpack a header.
Raises:
-
EndpointProtocolError–If the metadata length exceeds
MAX_META_SIZE.
Source code in proxystore/endpoint/protocol.py
Meta
¶
Bases: BaseModel
Base model of the metadata of a message.
Metadata is encoded as a JSON object. Unknown fields are ignored so that
fields can be added to a message without breaking older readers, and
bytes fields are hex-encoded.
decode
classmethod
¶
Decode from message metadata.
Empty metadata is decoded as an empty JSON object.
Raises:
-
EndpointProtocolError–If the metadata is malformed.
Source code in proxystore/endpoint/protocol.py
Versions
¶
Bases: Meta
ProxyStore and Python versions of a client or endpoint.
Attributes:
mismatches
¶
Find differences between this client's and the endpoint's versions.
The ProxyStore versions must have the same major version because proxies are compatible between versions with the same major version (see Versioning). The Python versions are not compared because endpoints do not deserialize objects.
Parameters:
-
endpoint(Versions) –Versions of the endpoint.
Returns:
Source code in proxystore/endpoint/protocol.py
mismatch_warning
¶
Describe the version mismatches between a client and an endpoint.
Parameters:
-
endpoint(Versions) –Versions of the endpoint.
Returns:
-
str | None–A warning describing the
-
str | None– -
str | None–and their consequences or
Noneif the versions are compatible.
Source code in proxystore/endpoint/protocol.py
decode
classmethod
¶
Decode from message metadata.
Empty metadata is decoded as an empty JSON object.
Raises:
-
EndpointProtocolError–If the metadata is malformed.
Source code in proxystore/endpoint/protocol.py
Hello
¶
Bases: Meta
First message of the handshake sent by the client.
Attributes:
decode
classmethod
¶
Decode from message metadata.
Empty metadata is decoded as an empty JSON object.
Raises:
-
EndpointProtocolError–If the metadata is malformed.
Source code in proxystore/endpoint/protocol.py
Challenge
¶
Bases: Meta
Reply of the endpoint to Hello.
Attributes:
-
nonce(Nonce) –Random nonce chosen by the endpoint.
-
proof(bytes) –Proof that the endpoint knows the token.
decode
classmethod
¶
Decode from message metadata.
Empty metadata is decoded as an empty JSON object.
Raises:
-
EndpointProtocolError–If the metadata is malformed.
Source code in proxystore/endpoint/protocol.py
Auth
¶
Bases: Meta
Second message of the handshake with the proof of the client.
Attributes:
-
proof(bytes) –Proof that the client knows the token.
decode
classmethod
¶
Decode from message metadata.
Empty metadata is decoded as an empty JSON object.
Raises:
-
EndpointProtocolError–If the metadata is malformed.
Source code in proxystore/endpoint/protocol.py
EndpointInfo
¶
Bases: Meta
Information about an endpoint sent at the end of the handshake.
Attributes:
-
id(EndpointId) –ID of the endpoint.
-
name(str) –Name of the endpoint.
-
versions(Versions) –Versions of the endpoint.
-
max_object_size(int | None) –Maximum size in bytes of objects that can be set on the endpoint or
Noneif there is no limit.
decode
classmethod
¶
Decode from message metadata.
Empty metadata is decoded as an empty JSON object.
Raises:
-
EndpointProtocolError–If the metadata is malformed.
Source code in proxystore/endpoint/protocol.py
Request
¶
Bases: Meta
Metadata of a request.
Requests are sent by clients to their local endpoint and by endpoints to their peers.
Attributes:
-
key(str | None) –Key of the object, or
Nonefor operations that do not operate on an object (e.g.,PING). -
target(EndpointId | None) –ID of the endpoint to forward the request to or
Nonefor the endpoint receiving the request.
decode
classmethod
¶
Decode from message metadata.
Empty metadata is decoded as an empty JSON object.
Raises:
-
EndpointProtocolError–If the metadata is malformed.
Source code in proxystore/endpoint/protocol.py
ExistsResult
¶
Bases: Meta
Result of an EXISTS request.
Attributes:
-
exists(bool) –If an object associated with the key exists.
decode
classmethod
¶
Decode from message metadata.
Empty metadata is decoded as an empty JSON object.
Raises:
-
EndpointProtocolError–If the metadata is malformed.
Source code in proxystore/endpoint/protocol.py
PingResult
¶
Bases: Meta
Result of a PING request.
Attributes:
-
peer_rtt_ms(float | None) –Time in milliseconds for the local endpoint to send a request to the peer and receive the response or
Noneif the local endpoint was pinged. The time of the first ping to a peer includes establishing the connection. -
relayed(bool | None) –If the connection to the peer is relayed, or
Noneif the path is unknown or the local endpoint was pinged. -
remote_addr(str | None) –Address of the peer (or relay) on the path.
-
path_rtt_ms(int | None) –Round-trip time in milliseconds of the path estimated by QUIC.
decode
classmethod
¶
Decode from message metadata.
Empty metadata is decoded as an empty JSON object.
Raises:
-
EndpointProtocolError–If the metadata is malformed.
Source code in proxystore/endpoint/protocol.py
ErrorInfo
¶
Bases: Meta
Metadata of a response with an error status.
Attributes:
-
error(str) –Message describing the error.
decode
classmethod
¶
Decode from message metadata.
Empty metadata is decoded as an empty JSON object.
Raises:
-
EndpointProtocolError–If the metadata is malformed.
Source code in proxystore/endpoint/protocol.py
Message
dataclass
¶
Message(
code: int,
meta: bytes = b'',
data: MessageData = b'',
request_id: int = 0,
)
A request or response message.
Attributes:
-
code(int) – -
meta(bytes) –JSON-encoded metadata of the message. Each operation or response defines the model of its metadata (e.g.,
Request). -
data(MessageData) –Data of the message.
-
request_id(int) –ID of the request (see
Header).
error_message
property
¶
error_message: str
Error message of a response with an error status.
This never raises so a malformed error response can still be reported.
error
classmethod
¶
from_error
classmethod
¶
from_error(error: BaseException) -> Message
Create a response for a request which failed with an error.
The status is determined by
error_status().
Source code in proxystore/endpoint/protocol.py
pack_head
¶
Pack the header and metadata of the message.
The data is not included so it can be sent separately without being copied.
Parameters:
-
data_len(int | None, default:None) –Length in bytes of the data sent after the head. Defaults to the length of
data. Set this when the data is not stored in the message (e.g., a view of a buffer sent by a client).
Source code in proxystore/endpoint/protocol.py
MessageReader
¶
MessageReader(*, max_data_size: int | None = None)
Parser of a message which does not perform I/O.
The caller reads exactly
size
bytes from the connection and passes them to
feed() until the
message is done.
Example
Parameters:
-
max_data_size(int | None, default:None) –Maximum size in bytes of the data of the message or
Nonefor no limit. The size is checked once the header and metadata are read, before the data is read.
Source code in proxystore/endpoint/protocol.py
size
property
¶
size: int
Size in bytes of the next part of the message to feed.
This is always greater than zero until the message is done.
feed
¶
Feed the next part of the message.
Parameters:
Raises:
-
RuntimeError–If the message is already done.
-
ValueError–If
bufferis not the expected size. -
EndpointProtocolError–If the header is malformed (the metadata is decoded by the receiver of the message).
-
ObjectSizeExceededError–If the size of the data exceeds the maximum. The
headercan be used to respond to the message.
Source code in proxystore/endpoint/protocol.py
HandshakeReader
¶
Bases: MessageReader
Parser of a handshake message which does not perform I/O.
Handshake messages never contain data, so a message with data is
rejected before its data is read (see
MessageReader).
Source code in proxystore/endpoint/protocol.py
size
property
¶
size: int
Size in bytes of the next part of the message to feed.
This is always greater than zero until the message is done.
feed
¶
Feed the next part of the message.
Raises:
-
EndpointProtocolError–If the message is malformed or contains data.
Source code in proxystore/endpoint/protocol.py
supports_version
¶
negotiate_version
¶
Negotiate the protocol version used with another client or endpoint.
Parameters:
-
newest(int) –Newest version supported by the other side.
Returns:
-
int | None–The newest version supported by both sides or
Noneif there is no common version.
Source code in proxystore/endpoint/protocol.py
alpn
¶
alpn_version
¶
Get the version of the peer protocol from its application protocol.
Raises:
-
ValueError–If
protocolis not an application protocol of ProxyStore.
Source code in proxystore/endpoint/protocol.py
supported_alpns
¶
Get the application protocols of the supported peer protocols.
Endpoints accept connections with any of these protocols and try each protocol when connecting to a peer, so the newest version supported by both peers is used.
Returns:
Source code in proxystore/endpoint/protocol.py
error_status
¶
error_status(error: BaseException) -> Status
Get the status of a response to a request which failed with an error.
Parameters:
-
error(BaseException) –Error raised while handling the request.
Returns:
-
Status–The status of the most specific type of
errorinSTATUS_ERRORSorStatus.ERRORif there is none.
Source code in proxystore/endpoint/protocol.py
raise_for_status
¶
Raise the exception corresponding to the status of a response.
Parameters:
-
response(Message) –Response message.
-
op(Op) –Operation of the request.
-
source(str, default:'Endpoint') –Description of the sender of the response for error messages.
Returns:
-
Status–
Raises:
-
EndpointRequestError–If the status is unknown (e.g., an error status added in a later version of the protocol).
-
EndpointError–The type in
STATUS_ERRORSfor any other status.