Scribble-Java tutorial
This is a tutorial for using the Scribble-Java toolchain (for Scribble version 0.4).
Contents:
-
About this tutorial
-
Quick Start: the Adder application protocol
-
Protocol overview
-
Scribble global protocol
-
An Adder client
-
An Adder server
-
Scribble modules and global protocols
-
Scribble modules
-
Message signatures
-
Global protocol declarations
-
The message interaction statement
-
The choice statement
-
The do statement
-
Further global protocol features
-
Message signature name declarations
-
The connection interaction statement
-
Scribble protocol validation
-
Valid global protocols and well-formed modules
-
Characteristics of Endpoint FSMs
-
Safety and liveness of Scribble global protocols
-
Endpoint API generation
-
Endpoint API overview
-
Names as singleton types
-
State Channel API
-
Output state API
-
Receive state API
-
Accept state API
-
Disconnect state API
-
Linear usage of state channel instances
-
Endpoint implementation using Scribble-generated APIs
-
Connection establishment for non-explicit global protocols
-
Usage contract of Endpoint APIs and endpoint implementation safety
-
Building/installing Scribble-Java
-
Running the Scribble-Java command line tool
Scribble-Java is a toolchain for programming distributed applications in Java based on the theory of multiparty session types.
The main input and output of the toolchain are:
Useful links and resources
-
Scribble home page
-
N.B. some of the tools currently available from
the home page are for an older version of Scribble than presented in
this tutorial. Those include:
-
Eclipse integration
-
Message trace simulation
-
Browser based tooling
-
Scribble-Java open source repository: GitHub
-
Some Scribble protocol and Java endpoint program examples: GitHub
Tutorial version history
- 0.4b
|
(Draft) revision for Scribble 0.4.
|
Upcoming in future tutorial versions
-
Scribble language:
-
Payload type and message signature name parameters for global protocol declarations
-
Messages in connection interaction statements
-
Endpoint API generation:
-
Branch callback API
-
Accept-branch API
-
Receive state futures
-
Abstract I/O state interfaces
-
Choice subtype interfaces
-
Host languages:
-
Scribble-Scala
-
Multiparty session delegation
Upcoming in future Scribble versions
-
Scribble language:
-
Protocol annotations
-
Message value assertions
-
Dynamic port actions (binary shared channel passing)
-
Endpoint API generation:
-
State name annotations
-
Event-driven API
-
Scribble Runtime API:
-
Additional transports: shared memory, HTTP
-
Host languages:
-
Scribble-Go
-
Role parameters for global protocol declarations
<
top;
next
>
This quick start will run through the specification of a simple
application protocol in Scribble, to Java implementations of client and
server endpoints using the Scribble-generated APIs.
<
prev;
top;
next
>
We illustrate a client-server protocol for a network service that adds two numbers.
An outline of the protocol:
-
The client C may choose to send to the server S one of the following two messages.
-
An Add message with a payload of two Ints.
-
After receiving the Add, S sends to C a Res message with a payload of one Int (the sum of the received Ints).
-
Then C and S continue by "looping" back to the start of the protocol.
-
A Bye message with an empty payload.
-
The protocol ends for C after sending.
-
The protocol ends for S after receiving.
<
prev;
top;
next
>
Below we give a Scribble specification of the Adder application protocol.
This code is available from the Scribble GitHub repository:
Adder.scr
Lst. 2.1: Scribble specification of the Adder application protocol.
|
- module tutorial.adder.Adder;
-
- type <java> "java.lang.Integer" from "rt.jar" as Int;
-
- global protocol Adder(role C, role S) {
- choice at C {
- Add(Int, Int) from C to S;
- Res(Int) from S to C;
- do Adder(C, S);
- } or {
- Bye() from C to S;
- }
- }
|
We use the above example to give a brief overiew of Scribble syntax. The links give further details of each element.
-
The payload type declaration
type <java> "java.lang.Integer" from "rt.jar" as Int;
declares java.lang.Integer as a Scribble payload type, Int, for use in this Scribble module.
-
In the global protocol declaration:
global protocol Adder(role C, role S) { ... }
-
Adder is the name of the protocol;
-
C and S are the two participants in this protocol.
-
The choice statement
choice at C { ... } or { ... }
states that the protocol proceeds according to one of the or-separated blocks.
-
In this example, C is the master role that decides which block to follow.
-
C communicates its decision to the slave S by the message passing in each case.
-
The message signature
Add(Int, Int)
specifies:
-
Add is the operator (i.e., a label or header that identifies the message);
-
(Int, Int) is a payload of two Ints.
-
In the message passing statement:
Add(Int, Int) from C to S;
-
C is the sender, which asynchronously dispatches the specified message (i.e., without blocking);
-
C is the receiver, which blocks until the message is received.
-
The do statement
do Adder(C, S);
allows a protocol to be defined in terms of other (sub)protocols. It is also used for recursive protocol definitions, as for Adder in this example.
<
prev;
top;
next
>
The command given below assumes:
In a Linux, Cygwin/Windows, or Mac OS terminal, the command is:
./scribblec.sh scribble-demos/scrib/tutorial/src/tutorial/adder/Adder.scr -api Adder C -d scribble-demos/scrib/tutorial/src/
The above command performs the following tasks.
-
Checks that the Scribble module in Adder.scr is a valid Scribble module -- this means that every global protocol (i.e., Adder) in the module must be a valid Scribble protocol (which it is).
-
Generates the Java Endpoint API for implementing the C endpoint of the Adder protocol. The output is written to the directory specified by the -d argument.
See here for more information on using the command line tool.
<
prev;
top;
next
>
We use the Endpoint API generated in the previous step to summarise the key elements behind Scribble Endpoint APIs.
-
The Endpoint API is generated from a finite state machine (FSM) representation of the I/O actions to be performed in the protocol by the target role.
-
An Endpoint API comprises a family of Java classes
that represent the states of the protocol relevant to the target role,
with methods for the I/O actions permitted in each state.
We then give an example implementation using the above Endpoint API.
<
prev;
top;
next
>
Below is the endpoint finite state machine (FSM) for the C role in Adder.
Fig. 2.3.1: The FSM for C in Adder.
|
-
State 1 is the initial state.
-
State 3 is the terminal state.
-
The notation S!Add(Int, Int) denotes the action by C to send S
a message with the specified signature.
-
The directed edge labelled by the above action means that performing this action in state 1 transitions C from to state 2.
-
The notation S?Res(Int) means C receives from S
a message of the specified signature.
<
prev;
top;
next
>
So that they may be used as types in Java, Scribble generates the various role and operator names in the source protocol as singleton types. For the Adder protocol, Scribble generates the following singleton types as API constants:
Roles
|
C,
S
|
Operators
|
Add,
Res,
Bye
|
The API generation renders each state in the Endpoint FSM as a Java class for a state-specific communication channel (which we call a state channel for short).
-
By default, Scribble names the state channel classes by a state
enumeration. For example, the class for the initial state is named Adder_C_1. The terminal state (if any) is generated as a specially named EndSocket class.
-
Each class is generated with methods to perform the I/O actions permitted at the corresponding state.
-
The return type of each method is the class generated for the successor state of the corresponding I/O action.
See here for an example Javadoc generated (by the standard javadoc tool) from the Scribble-generated API for C in Adder.
The following summarises the key state channel classes and their I/O methods.
Adder_C_1 (state
1 in the
FSM).
Type
|
Method
|
Adder_C_2
|
send(S role, Add op, Integer arg0, Integer arg1)
|
EndSocket
|
send(S role, Bye op)
|
-
The S, Add and Bye parameter types are the generated singleton types.
-
The send methods are non-blocking, i.e. they return immediately after asynchronously dispatching the specified message.
-
Calling a generated I/O method returns a new instance of the successor channel class.
Adder_C_2 (state
2 in the
FSM).
Type
|
Method
|
Adder_C_1
|
receive(S role, Res op, Buf<? super Integer> arg0)
|
-
Buf<T> is a utility class in the Scribble API.
-
It has a mutable field val of type T.
-
It has a public constructor Buf(T t), which initialises val to t.
-
The receive method blocks until the message is received. It then writes the received Integer value to the val field of supplied Buf before returning a new instance of the successor channel class.
We explain the Endpoint API for S later. It features the API generation for non-unary receive (i.e., branch) states, which is not demonstrated by the API for C.
<
prev;
top;
next
>
Usage contract of Scribble-generated Endpoint APIs
Starting from an instance of the initial state channel, an endpoint implemention should proceed by calling one method on the current channel to obtain the next, up to the end of the protocol (if any).
Below we give an example implementation of a C endpoint in an application of Adder. We use the Scribble-generated API following the above usage contract. This client uses the Adder service to calculate the n-th Fibonacci number.
The full code is available from the Scribble GitHub repository:
AdderC.java
Lst. 2.3.1: Java implementation of a C endpoint in Adder using the Scribble-generated API.
|
- package tutorial.adder;
-
- /* Imports omitted */
-
- public class AdderC {
-
- public static void main(String[] args) throws Exception {
- Adder adder = new Adder();
- try (MPSTEndpoint<Adder, C> client =
- new MPSTEndpoint<>(adder, C, new ObjectStreamFormatter())) {
- client.connect(S, SocketChannelEndpoint::new, "localhost", 8888);
- int n = 10;
- System.out.println(n + "th Fibonacci number: "
- + fibo(new Adder_C_1(client), n));
- }
- }
-
- private static int fibo(Adder_C_1 c1, int n) throws Exception {
- Buf<Integer> x = new Buf<>(0);
- Buf<Integer> y = new Buf<>(1);
- Buf<Integer> i = new Buf<>(1);
- while (i.val < n) {
- Adder_C_2 c2 = c1.send(S, Add, x.val, y.val);
- x.val = y.val;
- c1 = c2.receive(S, Res, y);
- c1 = add1(c1, i);
- }
- c1.send(S, Bye); // EndSocket
- return x.val;
- }
-
- private static Adder_C_1 add1(Adder_C_1 c1, Buf<Integer> i) throws Exception {
- return c1.send(S, Add, i.val, 1).receive(S, Res, i);
- }
- }
|
We summarise the key elements in the above code.
The main method contains a typical preamble for an implemention of a client endpoint using an Endpoint API.
-
A new session is created as an instance of the front-end Session Class:
Adder adder = new Adder();
The Session Class in this example, Adder, is part of the generated Endpoint API.
-
We create a session endpoint object for the role being implemented:
try (MPSTEndpoint<Adder, C> client =
new MPSTEndpoint<>(adder, C, new ObjectStreamFormatter())) { ... }
-
MPSTEndpoint<P, R> is a class in the base API of Scribble. It is parameterised on the generated API types designating the intended protocol, Adder, and the name of the target role, C.
-
The second constructor argument is an API constant of the generated type.
-
ObjectStreamFormatter,
provided by the Scribble API, performs the serialization and
deserialization of messages using the default Java object serialization
protocol.
-
The MPSTEndpoint is first used to establish a connection to the server endpoint S.
client.connect(S, SocketChannelEndpoint::new, "localhost", 8888);
connect is the client-side operation for requesting a connection.
-
S is an API constant of the type generated for role name S.
-
SocketChannelEndpoint::new is a constructor reference to a TCP channel class provided by the Scribble API.
-
"localhost" and 8888 give the address of the S server.
-
After establishing the required connection(s), the MPSTEndpoint is finally used to create the initial state channel:
new Adder_C_1(client)
The fibo method contains the main body of the protocol implementation using the Endpoint API explained earlier. It takes the initial state channel, Adder_C_1, and follows the protocol through to the terminal state channel, EndSocket, returned by c1.send(S, Bye).
-
The fibo method uses the Add option of the Adder service to add the the values of x and y for the next Fibonacci number.
-
It also uses add1 method to increment the the i counter.
<
prev;
top;
next
>
We give an example implementation of an S server for Adder. We highlight the features that are different or not shown in the example client implementation.
The full code is available from the Scribble GitHub repository:
AdderS.java
Lst. 2.3.2: Java implementation of a S endpoint in Adder using the Scribble-generated API.
|
- package tutorial.adder;
-
- /* Imports omitted */
-
- public class AdderS {
-
- public static void main(String[] args) throws Exception {
- try (ScribServerSocket ss = new SocketChannelServer(8888)) {
- while (true) {
- Adder adder = new Adder();
- try (MPSTEndpoint<Adder, S> server
- = new MPSTEndpoint<>(adder, S, new ObjectStreamFormatter())) {
- server.accept(ss, C);
- new AdderS().run(new Adder_S_1(server));
- } catch (ScribbleRuntimeException | IOException | ClassNotFoundException e) {
- e.printStackTrace();
- }
- }
- }
- }
-
- private void run(Adder_S_1 s1) throws Exception {
- Buf<Integer> x = new Buf<>();
- Buf<Integer> y = new Buf<>();
- while (true) {
- Adder_S_1_Cases cases = s1.branch(C);
- switch (cases.op) {
- case Add: s1 = cases.receive(Add, x, y)
- .send(C, Res, x.val+y.val); break;
- case Bye: cases.receive(Bye); return;
- }
- }
- }
- }
|
-
SocketChannelServer is used to accept TCP connections. It is a concrete implementation of ScribServerSocket, provided by the base API of Scribble.
-
The MPSTEndpoint and SocketChannelServer are used to establish a connection with the client C.
server.accept(ss, C);
-
Adder_S_1 is the initial state channel of the Endpoint API generated for S. This state is a non-unary receive state, or branch state.
The Adder_S_1 class has an instance method:
Adder_S_1_Cases branch(C role)
This method blocks until a message is received. It returns a new instance of Adder_S_1_Cases that has a field op of the generated type:
enum Branch_S_C_Add_Int_Int__C_Bye_Enum implements OpEnum {
Add, Bye
}
The name of the enum is a mechanically
derived name that does not need to be exposed in endpoint implementation
code in typical usage. The API sets the value of the op field of the returned Adder_S_1_Cases according to the operator of the received message.
Adder_S_1_Cases has the instance methods:
Adder_S_2 receive(C role, Add op, Buf<? super Integer> arg1, Buf<? super Integer> arg2)
EndSocket receive(C role, Bye op)
In each case of the switch(cases.op) statement, the corresponding method is used as a "cast" to obtain the appropriate state channel from the Cases object. The above methods are generated to throw a ScribbleRuntimeException if the wrong method is used.
<
prev;
top;
next
>
This section describes the syntax of Scribble modules and global protocols in more detail. It describes the syntactic constraints imposed by Scribble on global protocol definitions.
<
prev;
top;
next
>
A Scribble module is a file with the following elements, in order.
-
A module declaration:
module mypackage.MyModule;
-
The package prefix is optional.
-
The name of the file containing a Scribble module must be the same as the last element of the module name (MyModule) appended by the Scribble file extension (.scr); i.e., the module declared above should be contained in a file named MyModule.scr.
-
Zero or more import declarations:
import somepackage.subpackage.SomeModule;
-
Scribble-Java looks for a declared import along the import path, mapping the package prefix to a directory offset. E.g., for the above import declaration, Scribble-Java looks along the import path for a file somepackage/subpackage/SomeModule.scr.
-
Zero or more payload type declarations and message signature name declarations. The following explains payload type declarations.
type <java> "java.lang.Integer" from "rt.jar" as Int;
-
A payload type name (Int) may be used in the payload of a message signature.
-
java is the host language of the declared data type -- N.B. the current version of Scribble-Java only accepts Java (reference) types;
-
java.lang.Int is of name of the type in the host language;
-
rt.jar is the resource where the type definition is located -- N.B. the current version of Scribble-Java does not actually use this field (any string value may be specified, and will be ignored);
-
Int is a payload type name by which the type may be referred to.
Message signature name declarations are explained later in this tutorial.
-
Zero or more global protocol declarations.
The notion of well-formed module is discussed here.
<
prev;
top;
next
>
A message signature specifies the key elements of a message.
op ( pay1, ..., payn )
-
op is the message operator. Scribble uses the operator to identify the message. In the concrete message format, the operator may be conveyed as, e.g., a header field.
-
The operator may be omitted, denoting the empty operator.
-
( pay1, ..., payn ) is the message payload.
-
Each payi must be a payload type name.
-
The list of payload types may be the empty list, denoting the empty payload; i.e., ().
<
prev;
top;
next
>
A Scribble global protocol describes the interactions between the participants of a communication session from the global (i.e., neutral) perspective. A Scribble protocol abstracts session participants as named roles. Sessions are sometimes referred to as conversations.
Scribble protocols are strictly explicit about session
interactions -- the only application-level messages to be communicated
in a Scribble session are those that are explicitly specified in the
protocol.
global protocol MyProto(role r1, ..., role rn) {
...
}
-
MyProto is the name of the protocol.
-
role r1, ..., role rn is the role declaration list.
-
The list of roles must contain at least two roles. The role names must be distinct.
-
{ ... } is the protocol body.
<
prev;
top;
next
>
A message interaction is a role-to-role pairing of a send action and a receive action of a message.
The message may be specified as a message signature, i.e., an operator and a payload:
op(pay) from r1 to r2;
or as a message signature name:
Msg from r1 to r2;
In both cases, r1 is the sender role and r2 is the receiver role.
-
The sender and receiver roles must be distinct.
The communication model of Scribble is that message delivery is output-asynchronous, reliable, and ordered in each direction between each pair of roles. Output-asynchronous means the send action is non-blocking for the sender, but the receive action is blocking for the receiver (i.e., until the message arrives). This model caters for applications using, e.g., TCP channels, or (unbounded) FIFOs in shared memory.
Send actions are considered output actions, and receive actions are considered input actions.
<
prev;
top;
next
>
The choice statement designates a branch point in the protocol. The protocol proceeds according to one of the or-separated cases.
choice at r { ... } or ... or { ... }
-
The at-role, r, is called the choice subject.
-
The choice subject selects which block to follow as an internal choice. A Scribble protocol leaves the actual decision procedure abstract (it is an implementation-level concern).
-
The decision is to be communicated as an external choice to the other roles by appropriate message passing in each case.
A choice statement is subject to the following syntactic constraints.
-
Role enabling:
-
A role is either enabled or disabled.
-
On entering a choice, the only enabled role is the choice subject; all other roles that occur in the choice are disabled.
-
A role in a choice context may only send a message or request a connection if it is enabled.
-
A disabled role is enabled by receiving a message or accepting a connection (from an enabled role).
-
Consistent external choices:
-
For each role that occurs in the choice that is not the choice subject, the action kind (i.e., receive or accept) and interaction peer (i.e., the sender or client role) of the enabling action must be the same in all choice cases.
<
prev;
top;
next
>
The do statement instructs the specified roles to perform the interactions of the specified protocol inline with the current session.
do MyProto(r1, ..., rn);
-
MyProto is the name of a global protocol, referred to as the target protocol.
-
r1, ..., rn is the role argument list.
-
The length of role argument list must be the same as the length of the role declaration list of the target protocol declaration.
-
The role argument names must be distinct.
-
The meaning of the do statement is given by substituting every occurrence in the target protocol body
of a role in the role declaration list of the target protocol
declaration with the corresponding role in the role argument list.
In other words, each role in the role argument list plays the corresponding role in the role declaration list of the target protocol declaration.
<
prev;
top;
next
>
A do statement may be used to define (mutually) recursive protocols.
In addition to the previously stated constraints on do statements, Scribble global protocol definitions must be tail recursive per role. This means:
-
for any declared role r of a global protocol, it is not permitted for the protocol definition to sequence any action by r after a (mutually) recursive do statement that involves r.
<
prev;
top;
next
>
Message signature name declarations may occur alongside payload type declarations.
sig <java> "javapackage.JavaClass" from "myjar.jar" as MyMessage;
-
A message signature name (MyMessage) may be used in a message interaction. It represents a data type in the host language with custom message formatting, i.e., serialization and deserialization, routines.
-
java is the host language for which the message formatting routines for this message are implemented -- N.B. the current version of Scribble-Java only accepts concrete Java subclasses of the ScribMessage interface of the Scribble API;
-
javapackage.JavaClass is the host language name of the code unit (i.e. Java class) that contains the message formatting routines;
-
myjar.jar is the resource where the code message formatting code is located -- N.B. the current version of Scribble-Java does not actually use this field (any string value may be specified, and will be ignored);
-
MyMessage is the message signature name for this message format.
In message interactions that specify message signature names, the message signature name itself is used to identify the message (cf. the operator in a message signature).
<
prev;
top;
next
>
A global protocol declaration may have the modifier explicit.
explicit global protocol MyProto(...) { ... }
The communication model of a non-explicit global protocol starts with all roles pairwise connected by binary channels.
By contrast, the communication model of an explicit global protocol starts with all roles unconnected. A pair of roles are connected by:
connect r1 to r2;
A connection interaction is a role-to-role pairing of a connection request action and a connection accept action. Role r1 is the client role, and r2 is the server role.
-
The client and server roles must be distinct.
A connection interaction is a synchronous interaction between the client and server, i.e.,
the first role to perform either connection action is blocked until the
other role performs the counterpart action. The connection interaction
establishes a bidirectional binary (i.e., two-party) channel with the characteristics of the Scribble communication model, i.e. message delivery is reliable and ordered in each direction.
A pair of roles are disconnected by:
disconnect r1 and r2;
Each of the two disconnect actions in the above pairing is a local action, i.e.
it does not directly pertain to any interaction between the two roles.
A disconnect action closes the input queue at the local endpoint of the
binary channel between these two roles.
In the context of Scribble, request actions are considered output actions, and accept actions are considered input actions.
An explicit global protocol is subject to the following syntactic constraints.
-
A connection action is not permitted if the two roles are potentially connected.
-
A message action or a disconnect action is not permitted if the two roles are potentially unconnected.
This means there is at most one connection between any two roles.
<
prev;
top;
next
>
This section describes the notion of whether a global protocol is valid in Scribble.
<
prev;
top;
next
>
Scribble validates that a global protocol satisfies a set of desirable properties. These include safety properties, such as no reception errors (a role never receives an unexpected message), and liveness properties, such as eventual reception (a sent message can always eventually be received).
A Scribble global protocol is validated by two steps.
Specifically, the second step is conducted on a 1-bounded
model of the protocol. That is, the model given by the exhaustive
execution of the system of Endpoint FSMs where each communication
channel is limited to a maximum capacity of one message in each
direction.
-
A global protocol is syntactically well-formed if it satisfies the syntactic constraints of the first of the above steps.
-
A global protocol is valid if it satisfies both the above steps.
The Scribble validation of global protocols is sound. This means a valid protocol satisfies the safety and liveness properties in the general setting, i.e., the system of Endpoint FSMs with unbounded channels. Soundness is due to the characteristics of the protocol (i.e., the restrictions on endpoint behaviours) imposed by syntactic well-formedness.
Well-formed modules
A module is well-formed if every global protocol in the module is valid.
<
prev;
top;
next
>
The Endpoint FSM for any role of a syntactically well-formed Scribble global protocol has the following characteristics.
-
There is one initial state and at most one terminal state.
-
A state is one of the following kinds.
-
An output state. It has at least one transition. The action of every transition is either a send or a connect action.
-
An input state. It has at least one transition and is either:
-
a receive state, where all actions of its transitions are receive actions from the same role;
-
an accept state, where all actions of its transitions are accept actions from the same role;
-
A disconnect state. It has one transition. The action of the transition is a disconnect action.
-
The terminal state. It has no transitions.
<
prev;
top;
next
>
We first introduce some terminology.
-
A role is inactive in a session if it is at the initial state and is not connected to any role, or it is at the terminal state. Otherwise the role is active.
-
A session is in a final state if no role can perform any action.
A valid Scribble global protocol satisfies the following safety properties.
-
No reception errors. A message received by a role at a receive state is always one of the expected messages.
-
No orphan messages. A role that is inactive in a session has no outstanding messages to receive.
-
No unfinished roles. No role is left in an active state in a session that is in a final state.
A valid Scribble global protocol satisfies the following liveness properties.
-
Role progress. An active role can always eventually perform an action.
-
Eventual reception. A sent message can always eventually be received.
Fairness of output choice implementations
Scribble supports an option to consider unfair endpoint implementations of the choice between actions at output states. Here, "unfair" means that the implementation of the choice may never perform a particular action, even if the choice may be repeated infinitely often (i.e., in a recursive protocol).
For this option, Scribble adopts a "worst case" view where endpoint implementations exercise a minimal number of output choice options during session execution. This is affected by a model transformation that commits a role to perform again the same action whenever an output state is revisited.
See here for the instructions on enabling or disabling this option in the command line tool.
<
prev;
top;
next
>
This section describes the generation of protocol-specific Java Endpoint APIs by Scribble in more detail.
<
prev;
top;
next
>
An Endpoint API for a role in a global protocol has two parts.
-
The Session Class is the front-end class for creating a new session following the source protocol.
-
The State Channel API is a family of state-specific channel classes used to implement an endpoint playing the target role.
For a global protocol, e.g. Adder, in the Scribble module tutorial.adder.Adder, Scribble generates the Session Class as follows.
-
The Session Class is public class with the same name as the protocol, i.e., Adder.
-
The package of the Session Class is given by the module name and the protocol name, i.e., tutorial.adder.Adder.Adder;
-
The Session class has a no-args constructor:
public Adder()
An Endpoint API uses classes from the base Scribble API, which will be referred to in the following sections.
<
prev;
top;
next
>
A global protocol may feature several kinds of names: roles, message operators and message signature names.
Scribble generates a Java singleton type for each of these names. The following illustrates the code generated for the C role in Adder.
package tutorial.adder.Adder.Adder.roles;
public final class C extends org.scribble.sesstype.name.Role {
private static final long serialVersionUID = -6429099329627603991L;
public static final C C = new C();
private C() {
super("C");
}
}
-
C is the name of the class (i.e., of the singleton type).
-
C is also the name of the public static final field of type C in the class that refers to the singleton instance of the class.
-
The leading tutorial.adder.Adder in the package name derives from the source module name. The second Adder in the package name derives from the global protocol name.
The generation for message operators and message signature names is similar.
For convenience, the Session Class is also generated with field constants referring to the singleton type instances. E.g., the Adder Session Class has the following fields:
public static final C C = tutorial.adder.Adder.Adder.roles.C.C;
public static final S S = tutorial.adder.Adder.Adder.roles.S.S;
public static final Add Add = tutorial.adder.Adder.Adder.ops.Add.Add;
public static final Bye Bye = tutorial.adder.Adder.Adder.ops.Bye.Bye;
public static final Res Res = tutorial.adder.Adder.Adder.ops.Res.Res;
<
prev;
top;
next
>
The generation of a State Channel API for a role in a global protocol is based on the Endpoint FSM of that role.
Assume a valid global protocol G in a well-formed module mypack.MyMod. Let s be a state in the Endpoint FSM of G for role r.
If s is a terminal state in the Endpoint FSM, the Scribble generates:
-
a class named EndSocket with no I/O methods in package mypack.MyMod.G.channels.r.
N.B. Henceforth, references to a class G_r_s where s is a terminal state should be considered as EndSocket.
Otherwise (i.e., if s is not a terminal state):
<
prev;
top;
next
>
Let:
-
soutput be an output state in the Endpoint FSM of G for role r;
-
a1..n be the actions of the transitions from s, leading to states s1..n, respectively.
For each send action ai to receiver role r_i for message m:
-
If m is a message signature Op(Pay1, ..., Payn), Scribble generates an instance method in class G_r_s:
public G_r_si send(ri role, Op op, Pay1 arg1, ..., Payn argn) throws ScribbleRuntimeException, IOException
-
If m is a message signature name M, Scribble generates an instance method in class G_r_s:
public G_r_si send(ri role, M msg) throws ScribbleRuntimeException, IOException
The send methods are generated to return without blocking.
For each connect action ai to receiver role r_i, Scribble generates an instance method in class G_r_s:
public G_r_si connect(ri role, Callable<? extends BinaryChannelEndpoint> cons, String host, int port)
throws ScribbleRuntimeException, IOException
-
org.scribble.net.session.BinaryChannelEndpoint is an abstract class that represents a bidirectional binary communication channel. Implementations of this interface must provide a channel that is reliable and order-preserving in each direction.
-
org.scribble.net.session.SocketChannelEndpoint is a concrete implementation using TCP provided by the Scribble API.
-
host and port identify the address of the server for the connection request.
-
E.g., for SocketChannelEndpoint, host is an Internet host name and port a TCP port.
The connect method is generated to block until the connection is established or an exception is thrown.
<
prev;
top;
next
>
Let:
-
sreceive be a receive state in the Endpoint FSM of G for role r;
-
a1..n be the receive actions, from sender role r', of the transitions from s, leading to states s1..n, respectively.
If s is a unary receive state, i.e., n = 1, and a1 specifies message m, then:
-
If m is a message signature Op(Pay1, ..., Payn), Scribble generates an instance method in class G_r_s:
public G_r_s1 receive(r' role, Op op, Buf<? super Pay1> arg1, ..., Buf<? super Payn> argn)
throws ScribbleRuntimeException, IOException, ClassNotFoundException
-
If m is a message signature name M, Scribble generates an instance method in class G_r_s:
public G_r_s1 receive(r' role, Buf<? super M arg1)
throws ScribbleRuntimeException, IOException, ClassNotFoundException
The receive methods are generated to block until a message is received.
Otherwise, i.e., n > 1, Scribble generates:
The branch method is generated to block until a message is received.
For each (receive) action ai from sender r' for message mi:
-
If mi is a message signature Op(Pay1, ..., Payn), Scribble generates an instance method in class G_r_s_Cases:
public G_r_si receive(r' role, Op op, Buf<? super Pay1> arg1, ..., Buf<? super Payn> argn)
throws ScribbleRuntimeException, IOException, ClassNotFoundException
-
If mi is a message signature name M, Scribble generates an instance method in class G_r_s_Cases:
public G_r_si receive(r' role, Buf<? super M arg1)
throws ScribbleRuntimeException, IOException, ClassNotFoundException
org.scribble.net.Buf<T> is a utility class in the Scribble API.
-
It has a mutable field val of type T.
-
It has a public constructor Buf(T t), which initialises val to t.
The receive methods in both of the above cases are generated to write the each of the received payload values or message object to the val field of the corresponding Buf argument before returning.
<
prev;
top;
next
>
Let:
-
saccept be an accept state in the Endpoint FSM of G for role r;
-
a1..n be the actions of the transitions from s, leading to states s1..n, respectively.
Scribble generates an instance method in class G_r_s:
public G_r_s1 accept(ri role, ScribServerSocket ss) throws ScribbleRuntimeException, IOException
-
org.scribble.net.scribsock.ScribServerSocket is an abstract class that represents a server port for accepting BinaryChannelEndpoint connection requests.
-
org.scribble.net.scribsock.SocketChannelServer is a concrete implementation using TCP provided by the Scribble API.
The accept method is generated to block until the connection is established or an exception is thrown.
<
prev;
top;
next
>
If s is a disconnect state in the Endpoint FSM of G for role r:
<
prev;
top;
next
>
Scribble enforces a linear usage discipline on state channel
instances, by generating run-time checks into the Endpoint API. This
means an endpoint implementation should invoke one I/O method on every instance of a state channel, apart from EndSocket. The following situations throw a ScribbleRuntimeException.
-
An I/O method is invoked on a state channel instance on which an I/O method has been previously invoked.
-
A try-with-resources statement closes an MPSTEndpoint declared in a resource specification and there exists an associated state channel instance on which no I/O method has been called.
<
prev;
top;
next
>
This section describes additional details of endpoint implementation
using Scribble-generated APIs and the associated safety properties.
<
prev;
top;
next
>
Endpoint implementation using an Endpoint API generated from a non-explicit global protocol relies on a connection establishment phase between creating the MPSTEndpoint and the initial state channel.
Client-side connection requests
The following is from the example implementation of a C client in Adder.
Adder adder = new Adder();
try (MPSTEndpoint<Adder, C> client =
new MPSTEndpoint<>(adder, C, new ObjectStreamFormatter())) {
client.connect(S, SocketChannelEndpoint::new, "localhost", 8888);
/* Body of endpoint implementation, starting from new Adder_C_1(client)*/
}
Client-side connection requests are peformed by the connect method of MPSTEndpoint.
client.connect(S, SocketChannelEndpoint::new, "localhost", 8888);
The above method has the same signature as the State Channel API connect method generated for explicit connect actions.
-
S is the singleton type constant generated for the role being connected to.
-
SocketChannelEndpoint::new is a constructor reference, passed as parameter of expected type Callable<? extends BinaryChannelEndpoint>.
-
"localhost" and 8888 are the hostname and port arguments for the SocketChannelEndpoint connection request.
Server-side connection accepts
The following is from the example implementation of a S server in Adder.
try (ScribServerSocket ss = new SocketChannelServer(8888)) {
while (true) {
Adder adder = new Adder();
try (MPSTEndpoint<Adder, S> server
= new MPSTEndpoint<>(adder, S, new ObjectStreamFormatter())) {
server.accept(ss, C);
new AdderS().run(new Adder_S_1(server));
} catch (ScribbleRuntimeException | IOException | ClassNotFoundException e) {
e.printStackTrace();
}
}
}
Server-side connections are accepted via an instance of org.scrible.net.scribsock.ScribServerSocket.
try (ScribServerSocket ss = new SocketChannelServer(8888)) { ... }
-
org.scribble.net.scribsock.SocketChannelServer is a concrete implementation of ScribServerSocket for accepting the server-side of TCP connections.
A connection is accepted by the accept method of MPSTEndpoint.
server.accept(ss, C);
-
ss is the SocketChannelServer, passed as a parameter of expected type ScribServerSocket.
-
C is the singleton type constant generated for the role to accept the connection from.
Safety warning for non-explicit connection actions
The actions performed in a non-explicit connection phase are not verified in any way. This means it is up to the programmer to avoid errors such as the following.
-
Incorrect connection request or accept actions, i.e., performing a connect instead of an accept or vice versa, or attempting a connect or accept with the wrong role.
-
Deadlock, e.g., due to a wait-for cycle between two or more roles mutally blocked on connect and accept actions.
<
prev;
top;
next
>
The following is the API usage contract for endpoint implementations using Scribble-generated Endpoint APIs.
Starting from an instance of the initial state channel, an endpoint implemention should proceed by calling one method on the current channel to obtain the next, up to the end of the protocol (if any).
An Endpoint API is generated to throw a ScribbleRuntimeException at run-time if the above rule, i.e. linear usage of state channels, is violated. The I/O action of the offending method invocation will not be performed.
Note that the only way that an endpoint implementation using an Endpoint API may attempt
to violate the protocol is by violating state channel linearity. This
confers the following form of safety for Scribble endpoint
implementations.
-
If linear channel usage is respected, then Java type checking statically guarantees that the endpoint implementation complies to the specified protocol.
-
Regardless of linear channel usage, Java type checking
guarantees protocol compliance up to premature termination of the
endpoint in the session.
<
prev;
top;
next
>
The source code of the master version of Scribble-Java is available from GitHub.
We give basic instructions for two ways of building the Scribble-Java tool from source. Both ways will create the file
dist/target/scribble-dist-0.4-SNAPSHOT.zip
under the root directory of the scribble-java project.
Building Scribble-Java using command line Maven
These instructions have been tested on Ubuntu 16.04.2 using:
-
OpenJDK 1.8.0_221
-
Apache Maven 3.3.9
From the root directory of the scribble-java project:
Building Scribble-Java using Eclipse
These instructions have been tested for:
-
Ubuntu 16.04.2, using Eclipse Luna 2 (4.4.2).
-
Windows 10, using Eclipse Neon.2 (4.6.2).
-
Mac OS X Yosemite, using Eclipse Luna 2 (4.4.2).
N.B. Eclipse must be configured to use a JDK 8 (Preferences → Java → Installed JREs).
Steps:
-
Import the Scribble-Java projects as Existing Maven Projects.
-
Eclipse should compile Scribble automatically.
-
You may be prompted to install a Maven-Eclipse plugin (M2E connector) for ANTLR.
-
Right click, e.g., scribble-java in the Package Explorer → Run As → Maven install
Installing the Scribble-Java command line tool
The scribble-dist-0.4-SNAPSHOT.zip built by the above instructions contains:
-
scribblec.sh -- the command line script to run the tool;
-
lib -- a directory of jars for running the tool.
The script is immediately executable after extracting the zip contents.
Alternatively, the script is executable from the scribble-java root directory, without lib, by:
-
building the project (as above);
-
and setting the ANTLR variable in the script to provide the Java classpath for an ANTLR 3 runtime.
<
prev;
top;
next
>
Assuming the scribblec.sh script is installed at the current directory, various command line options are shown by:
./scribblec.sh --help
The following summarises a few options mentioned in this tutorial:
-ip import_path
|
Sets the import path for module imports.
|
-fair
|
Disables the model transformation and checking for "unfair" endpoint implementations.
|
A command for validating the example Adder protocol and generating the API for the C role was given here.
Further examples of using the scribblec.sh script can be found in this README.
<
prev;
top;
>