Framework Overview: MPST in Rust. Our design resembles the top-down methodology of multiparty session types, as illustrated in Fig. 1. It follows three main steps [5, 17]. First, a global type, also called a global protocol, is defined as a shared contract between communicating endpoint processes. A global protocol is then projected to each endpoint process, resulting in a local type. A local type involves only the interactions specific to a given endpoint. Finally, each endpoint process is type-checked against its projected local type.
The specific parts of our framework that distinguish it from other state-of-the-art MPST works are highlighted in red, which corresponds to our new library for MPST programming in Rust, mpst-rust. It is realised as a thin wrapper on top of an existing Rust library for validation of binary (2-party-only) session types. Developers use the MPST primitives provided by mpst-rust to implement endpoint programs. Also, our framework allows the types for each communication primitive to be either (1) generated from the Scribble toolchain; or (2) written by the developers. The Scribble toolchain [18] provides facilities for writing, verifying and projecting global protocols. Our framework guarantees that processes implemented using mpst-rust primitives with Scribble-generated types are free from deadlocks, reception errors, and protocol deviations. Next, we explain, via an example, how the framework of MPST can be applied to Rust.
Example: Amazon Prime Video Streaming. The Amazon Prime Video streaming service is a usecase which can take full advantage of multiparty session types. Each streaming application connects to servers, and possibly other devices, to access services, and follows some specific protocol. To present our design, we use a simplified version of the protocol, illustrated in the diagram in Fig. 1 (right). The diagram should be read from top to bottom. The protocol involves three services – an
service, a
and a
. At first,
connects to
by providing an identifying id. If the id is accepted, the session continues with a choice on
to either request a video or end the session. The first branch is, a priori, the main service provided by Amazon Prime Video.
cannot directly request videos from
, and has to go through
instead. On the diagram, the choice is denoted as the frame alt and the choices are separated with the horizontal dotted line. The protocol is recursive, and
can request new videos as many times as needed. The arrow going back on
side in Fig. 1 represents this recursive behaviour. To end the session,
first sends Close message to
, which then subsequently sends a Close message to
.
Implementing the Authenticator role Using mpst-rust . Due to space limitations, we only show the implementation of the
role (hereafter role
), the implementations of the other roles (role
for the
and role
for the
) are similar. The Rust code for role
using the mpst-rust library is given in Fig. 2 (left). It closely follows the local protocol in Fig. 2 (right), that is projected from the global protocol by the Scribble toolchain. First, line 1 declares a function authenticator that is parametric in a multiparty channel s of type VideoP_A. The type VideoP_A specifies which operations are allowed on s. This type can either be written by the developer, or generated by Scribble (cf. Listing 1).
On line 3,
receives an identifying id from
. The function
, provided by mpst-rust library returns the received value (the id) and the new multiparty channel, to be used in subsequent communications. Line 3 rebinds the multiparty channel s with the new channel that is returned. Then, on line 4, we send back the answer to C, by utilising another mpst-rust communication primitive,
. The variable s is rebound again to the newly returned multiparty channel. Note that although the name of the function,
, suggests a binary communication, the function operates on a multiparty channel s. Our implementation follows the encoding, presented in [13], which encodes a multiparty channel as an indexed tuple of binary channels. Internally,
extracts from s the binary channel established between
and
and uses it for sending.
Lines 9–26 proceeds by implementing the recursive part of the protocol. The implementation of
realises an internal choice – A can either receive a VideoRequest or a Close. This behaviour is realised by the mpst-rust macro
(line 12), which is applied to a multiparty channel s of a sum type between ChoiceA::Video and ChoiceA::End. The behaviour of each branch in the protocol is implemented as an anonymous function. For example, code in lines 13–21 supplies an anonymous function that implements the behaviour when
sends a VideoRequest, while lines 22–26 handle the Close request. Finally,
closes all binary channels stored inside s. The types of the multiparty channel, as well as the generic types in the declaration of the mpst-rust communication functions, enable compile-time detection of protocol violations, such as swapping line 3 and line 4, using another communication primitive or using the wrong payload type.
Typing the Authenticator Role. The types for the
role, used in Fig. 2 (left), are given in Listing 1. These types can be either written by the developer or generated from a global protocol, written in Scribble. Reception error safety is ensured since the underlying mpst-rust library checks statically that all pairs of binary types are dual to each other. Deadlock-freedom is ensured only if types are generated from Scribble since this guarantees that types are projected from a well-formed global protocol.
Next, we explain a type declaration for the Authenticator role. Lines 27–37 specify the three SessionMpst types which correspond to the types of the session channels used in Fig. 2 (left) – types VideoP_A (line 1), Video_PRec_A (line 9), and the types used inside the offer construct – ChoiceA::Video (line 13), and ChoiceA::End (line 22).
In the encoding of [13], which underpins mpst-rust, a multiparty channel is represented as an indexed tuple of binary channels. This is reflected in the implementation of SessionMpst, which is parameterised on the required binary session types. For example, the \(\texttt {VideoP\_A{<}N{>}}\) takes as a parameter the binary types between A and C, and between A and B. At the beginning of the protocol (lines 1–7 in Fig. 2 (left)) B and A do not interact, hence the binary type for B is End. The type \(\texttt {InitA{<}N{>}}\) (line 2 in Listing 1) specifies the behaviour between A and C, notably that A first receives a message, then it sends a message, and later it continues as the type \(\texttt {RecvChoice{<}N{>}}\). The binary session types between A and B, and between A and C are given in lines 12–14 and lines 2–9 respectively; we use the primitives declared in the existing binary session types library [9]. The generic parameter
refers to a
such as
.
The third parameter for \(\texttt {VideoP\_A{<}N{>}}\) (line 27) is a queue-like data structure, QueueAInit (line 17), that codifies the order of usage of each binary channel inside a multiparty session channel. This is needed to preserve the causality, imposed by the global protocol. The queues for the other SessionMpst types are given in lines 21–24. For instance, the queue for the ChoiceA:Video branch of the protocol is QueueAVideo. Note that, according to the protocol, A first has to receive a VideoRequest message from C, and then it has to forward that message to B Hence, swapping of lines 17 and 18 from Fig. 2 is a protocol violation error. We can detect such violations since the queue for the type ChoiceA::Video, QueueAVideo (line 21), is specified as \(\texttt {RoleAtoC{<}RoleAtoB ...{>}}\), which codifies that first the channel for C and then the channel for B should be used. Note that none of the defined queues is recursive. Recursion is implicitly specified on binary types, while each queue is related to a SessionMpst type.
Distributed Execution Environment. The default transport of mpst-rust is the built-in Rust communication channels (crossbeam_channel). Also, to test our example in a more realistic distributed environment, we have also connected each process through MQTT (MQ Telemetry Transport) [7]. MQTT is a messaging middleware for exchanging messages between devices, predominantly used in IoT networks. At the start of the protocol, each process connects to a public MQTT channel, and a session is established. Therefore, we have mapped binary channels to MQTT sockets, in addition to the built-in Rust channels.