Project Etiquette Quick Start Guide


Table of Contents

  1. Introduction
  2. Concepts and terminology
  3. Etiquette syntax
  4. Running Etiquette
  5. Step-by-step example
  6. Future work
  7. Authors

Introduction

The project's goal is to build a framework for rapid design of network communication code. Etiquette interaction protocols will capture communication occurring in networked applications along with error-handling and security monitoring code. Etiquette provides debugging facilities (tracing and single-stepping the protocols, with fine-grained control over the reported details). Features implemented:

Etiquette is a developing project, and a number of other important features is expected to be added in near future.

Etiquette is implemented in Common Lisp, and is distributed under the terms of FSF General Public License (if you feel that this license is too strict for your needs, please contact the author). The latest version of Etiquette can be obtained from its SourceForge project page.

Concepts and terminology

The project aids to abstract the communication processes occuring in distributed and networked systems.

We will use the following terms in the document:

Technically, a protocol is a state machine. For it to run, the protocol should be instantiated and started by both parties involved.

Etiquette Syntax

Etiquette defines a handful of syntactic constructions that can be used to define interaction protocols.

The major syntactic form is DEFPROTOCOL:

(defprotocol name (&key transport subprotocols error-handling-policies) body-form*)

Here, NAME specifies the protocol name, and a sequence of BODY-FORM forms constitutes the protocol's control flow description. The protocol can be passed a TRANSPORT layer it will use for communication, a list of SUBPROTOCOLS it can execute, and ERROR-HANDLING-POLICIES. All forms in protocol body are executed sequentially.

Within the protocol body, two syntactic forms can be used:

TRANSFER defines an unconditional transfer phase from agent in role FROM to agent in role TO. NAME defines the phase name.

TRANSFER-IF defines a conditional transfer phase from agent in role FROM to agent in role TO. NAME defines the phase name. If the condition denoted by PREDICATE is true on FROM agent side, the CONSEQUENCE is executed, otherwise ALTERNATIVE is executed.

TRANSFER-CASE implements a selector construct. CASE-FORM is a list of match and consequence. The result of SELECTOR invocation is compared via EQ with matches of case-forms, and in case of success, the appropriate consequence is invoked.

TRANSFER-WHILE defines an iterating phase. The ITERATED-FORM is performed while the PREDICATE at the FROM side holds true. As soon as the loop terminates, the mandatory EXIT-FORM is executed and control is passed to a next phase. The EXIT-FORM is mandatory for communication consistency reasons: consider that the protocol code is simultaneously executed by two different agents, in different roles.

TRANSFER-FORM, ITERATED-FORM, EXIT-FORM, CONSEQUENCE and ALTERNATIVE can be either symbols naming corresponding agent methods, or subprotocol invocation forms. The latter has the following syntax:

(subprotocol protocol-name)

where PROTOCOL-NAME is a symbolic name of the protocol to be invoked.

Running Etiquette

Prerequisites

To run Etiquette you will need the following items installed on your computer:

Running a protocol

Etiquette distribution includes a sample code of echo protocol implementation.

To run the server-side agent:

  1. Run the lisp compiler in Etiquette source directory
  2. Enter (asdf:oos 'asdf:load-op 'etiquette)
  3. Enter (asdf:oos 'asdf:load-op 'etiquette-samples)
  4. Enter (in-package #:simple-echo)
  5. Enter (run-agent (make-instance 'echo-server))

To run the client-side agent:

  1. Run the lisp compiler in Etiquette source directory
  2. Enter (asdf:oos 'asdf:load-op 'etiquette)
  3. Enter (asdf:oos 'asdf:load-op 'etiquette-samples)
  4. Enter (in-package #:simple-echo)
  5. Enter (run-agent (make-instance 'echo-client))

Notice that there are also examples of an HTTP subset, file retrieval protocol and non-conformant SMTP subset included.

When echo client starts, it should connect to the server. Both client and server ask you for protocol termination at each iteration. If you choose to terminate a protocol, the subportocol QUIT is invoked.

Step-by-step example

Let's examine how the above mentioned echoing protocol and associated agents were defined.

First, we lay out the generic sketch of the protocol:

(defprotocol fancy-echo ()
 (transfer query-phase (:from :initiator :to :respondent) query)
 (transfer response-phase (:from :respondent :to :initiator) response))

It is blindly obvious and does the job. However, we would like to loop the protocol:

(defprotocol fancy-echo ()
 (transfer query-phase (:from :initiator :to :respondent) query)
 (transfer response-phase (:from :respondent :to :initiator) response)
 (transfer restart-phase (:from :initiator :to :respondent) (subprotocol fancy-echo)))

As you can see, we have implemented an unconditional loop via recurrent invocation of FANCY-ECHO. But Etiquette has a better way of dealing with iteration:

(defprotocol echo-controller
 ()
 (transfer-while iteration-phase (proceed :from :initiator :to :respondent)
    (subprotocol fancy-echo)
    (subprotocol quit)))

We defined a wrapping protocol that invokes FANCY-ECHO in a loop. The TRANSFER-WHILE form will execute FANCY-ECHO while the initiator maintains PROCEED conditional true. When iteration is done, the subprotocol QUIT is invoked.

Now, what should we do if the server is bored with echo requests and wants to terminate the session?

(defprotocol fancy-echo ()
 (transfer query-phase (:from :initiator :to :respondent)
    query)
 (transfer-if response-phase (terminate :from :respondent :to :initiator)
    (subprotocol quit)     response))

We have just modified the RESPONSE-PHASE clause to be conditional. When this phase is executed, respondent agent's TERMINATE method is invoked, and if it evaluates to NIL, the response is transferred. Otherwise, the QUIT subprotocol is invoked.

We should also specify the default network transport for the agents.

(defprotocol echo-controller
  (:transport (make-transport 'inet-tcp-transport :specifier 2345)) ;in this case, a port number
 (transfer-while iteration-phase (proceed :from :initiator :to :respondent)
    (subprotocol fancy-echo)
    (subprotocol quit)))

Now, to the QUIT protocol. We can write it this way: (defprotocol quit ()
   (transfer bye-phase (:from :initiator)
    bye)
  (terminate))

TERMINATE clause here will cause communication termination at both sides.

At this point, the protocol code can be compiled. Next we should define a couple of agents that will use the protocols:

(defclass echo-server (communication-agent)
  ((echo-reply :initform nil
    :accessor echo-reply)))

(defclass echo-client (communication-agent)
  ((echo-reply :initform nil
    :accessor echo-reply)))

Then, the agents should implement RUN-AGENT-PROTOCOL methods that do all the necessary things to run a given protocol:

(defmethod run-agent-protocol ((server echo-server) (proto echo-controller))
  (set-destination-spec proto '("localhost" 2345))
  (execute-protocol proto :tracing t))

(defmethod run-agent ((server echo-server))
  (let* ((proto (make-protocol 'echo-controller server :respondent)))
    (run-agent-protocol server proto)))

(defmethod run-agent ((client echo-client))
  (let* ((proto (make-protocol 'echo-controller client :initiator
      :destination-spec '("localhost" 2345))))
  (run-agent-protocol client proto)))

(defmethod run-agent-protocol ((client echo-client) (proto echo-controller))
  (set-destination-spec proto '("localhost" 2345))
  (execute-protocol proto :tracing t))

Now, how all these QUERY, RESPONSE and PROCEED things are actually handled? We should provide an implementation for methods returning a value on :INITIATOR side, and methods that accept the received data on RESPONDENT side. Currently both sending and receiving methods have same signature, and sender methods just ignore the optional DATA argument.

Predicates shall return NIL or non-NIL value based on the agent's internal state.

;;; query receiving part
(defmethod query ((server echo-server) (protocol fancy-echo) (role (eql :respondent)) buffer)
  (setf (echo-reply server) (format nil "ECHOING: ~A" data)))

;;; response sending part
(defmethod response ((server echo-server) (protocol fancy-echo) (role (eql :respondent)) buffer)
  (echo-reply server))

;;;query sending part
(defmethod query ((client echo-client) (protocol fancy-echo) (role (eql :initiator)) buffer)
  "A quick brown fox jumps over the lazy dog")

;;; response receiving part
(defmethod response ((client echo-client) (protocol fancy-echo) (role (eql :initiator)) buffer)
  (format t "~%Server responds with: ~A" data))

;; a conditional at client side: notice that it is specialized for echo-controller
(defmethod proceed ((client echo-client) (protocol echo-controller) (role (eql :initiator)))
  (unless (y-or-n-p "Shall the client proceed? (y/n) ")
    (break))

;; a conditional at server side
(defmethod terminate ((server echo-server) (protocol fancy-echo) (role (eql :respondent)))
  (y-or-n-p "Shall the server terminate? (y/n) "))

Future work

Etiquette is a work in progress, so several other major features and improvements are expected:


Authors

Etiquette was written by Eugene Zaikonnikov. The project could never been done without great insights provided by Stig Erik Sandoe and Knut Arild Erstad.

Last modified Tuesday, 09-Sep-2003