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.
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 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 name (&key from to) transfer-form)
(transfer-if name (predicate &key from to) consequence
alternative)
(transfer-case name (selector &key from to) case-form*)
(transfer-while name (predicate &key from to) iterated-form
exit-form)
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.
To run the server-side agent:
(asdf:oos 'asdf:load-op 'etiquette)
(asdf:oos 'asdf:load-op 'etiquette-samples)
(in-package #:simple-echo)
(run-agent (make-instance 'echo-server))
To run the client-side agent:
(asdf:oos 'asdf:load-op 'etiquette)
(asdf:oos 'asdf:load-op 'etiquette-samples)
(in-package #:simple-echo)
(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.
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: 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? 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. Now, to the QUIT protocol. We can write it this way:
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: Then, the agents should implement RUN-AGENT-PROTOCOL methods that
do all the necessary things to run a given protocol: 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. Etiquette is a work in progress, so several other major features
and improvements are expected:
Etiquette was written by Eugene
Zaikonnikov. The project could never been done without great
insights provided by Stig Erik Sandoe and Knut Arild Erstad.
(defprotocol echo-controller
 ()
 (transfer-while iteration-phase (proceed :from :initiator :to :respondent)
    (subprotocol fancy-echo)
    (subprotocol quit)))
(defprotocol fancy-echo ()
 (transfer query-phase (:from :initiator :to :respondent)
    query)
 (transfer-if response-phase (terminate :from :respondent :to
:initiator)
    (subprotocol quit)
    response))
(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)))
(defprotocol quit ()
 
 (transfer bye-phase (:from :initiator)
    bye)
  (terminate))
(defclass echo-server (communication-agent)
  ((echo-reply :initform nil
    :accessor echo-reply)))
(defclass echo-client (communication-agent)
  ((echo-reply :initform nil
    :accessor echo-reply)))
(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))
;;; 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
Authors
Last modified Tuesday, 09-Sep-2003