From: Frederic Brunel
Subject: Implementing a simple network protocol
Date: 
Message-ID: <3c7c1135$0$14891$626a54ce@news.free.fr>
Hi dudes,

I would like to have your opinion about the implementation of a simple
network protocol with Common Lisp. I've done a TFTP (RFC 1350)
implementation in Java for my job and I've faced some design issues.
Since the TFTP protocol could be easily modeled by an automata, my
idea is that I could implement it using a finite state machine with
Common Lisp which seems perfectly suited for this kind of job!

Do you think it's a good solution for implementing this protocol and
do you have any advices for doing it?

--
Frederic Brunel

From: Matthieu Villeneuve
Subject: Re: Implementing a simple network protocol
Date: 
Message-ID: <3C7C2778.DAA43151@tumbleweed.com>
How about having a "transition" structure having: start and end states
(plain integers), label (the protocol commands), and a function
evaluated when going through the transition?

The current state of the automaton has to be kept for each connection,
along with other information, to maintain a kind of session.

When receiving a command, the server gets the corresponding transition,
for example from a string-indexed hashtable, checks that the current
state of the session is equal to the transition's start state, then
evaluates the transition's function with the relevant arguments, and
sets the current state to the transition's end state.

Following is some toy code playing with that idea, and with a simple
protocol supporting only 3 commands HELLO, GET, QUIT (of course, much
should be added to the code to check argument number, etc.)


(defstruct transition
  start
  end
  label
  function)

(defvar *atm* (make-hash-table :test 'equal))

(defvar *current-state* 0)

(defun add-transition (start end label function)
  (let ((transition (make-transition :start start
                                     :end end
                                     :label label
                                     :function function)))
    (setf (gethash label *atm*) transition))
  *atm*)

(defun handle-command (command)
  (let* ((tokens (split-string command #\Space))
         (label (first tokens))
         (args (rest tokens))
         (trans (gethash label *atm*)))
    (when (null trans)
      (error "Unknown command"))
    (when (/= (transition-start trans) *current-state*)
      (error "Incorrect state"))
    (apply (transition-function trans) args)
    (setf *current-state* (transition-end trans))))

(add-transition 0 1 "HELLO" 
                #'(lambda (name) (format t "Start session with ~A~%"
name)))

(add-transition 1 1 "GET"
                #'(lambda (file) (format t "Get file ~A~%" file)))

(add-transition 1 2 "QUIT"
                #'(lambda () (format t "Quit...~%")))



--Matthieu


Frederic Brunel wrote:
> 
> Hi dudes,
> 
> I would like to have your opinion about the implementation of a simple
> network protocol with Common Lisp. I've done a TFTP (RFC 1350)
> implementation in Java for my job and I've faced some design issues.
> Since the TFTP protocol could be easily modeled by an automata, my
> idea is that I could implement it using a finite state machine with
> Common Lisp which seems perfectly suited for this kind of job!
> 
> Do you think it's a good solution for implementing this protocol and
> do you have any advices for doing it?
> 
> --
> Frederic Brunel
From: Matthieu Villeneuve
Subject: Re: Implementing a simple network protocol
Date: 
Message-ID: <3C7C5C2E.8AE6A27A@tumbleweed.com>
Actually, it would be better not to have the start and end states in the
transition, but to have them checked and set by the transition's
function, to facilitate error handling. Here is the modified code:


(defstruct transition
  label
  function
  arg-count-min
  arg-count-max)

(defvar *atm* (make-hash-table :test 'equal))

(defvar *current-state* 0)
(defvar *current-user* nil)

(defun add-transition (label arg-count-min arg-count-max function)
  (let ((transition (make-transition :label label
                                     :function function
                                     :arg-count-min arg-count-min
                                     :arg-count-max arg-count-max)))
    (setf (gethash label *atm*) transition))
  *atm*)

(defun handle-command (command)
  (let* ((tokens (split-string command #\Space))
         (label (first tokens))
         (args (rest tokens))
         (trans (gethash label *atm*)))
    (when (null trans)
      (error "Unknown command"))
    (when (not (<= (transition-arg-count-min trans)
                   (length args)
                   (transition-arg-count-max trans)))
      (error "Incorrect argument number"))
    (apply (transition-function trans) args)))

(add-transition 
 "HELLO" 1 1
 #'(lambda (name)
     (cond ((= *current-state* 0)
            (setf *current-user* name)
            (format t "Starting session with ~A~%" name)
            (setf *current-state* 1))
           (t
            (format t "Error: another user already logged in~%")))))

(add-transition 
 "GET" 1 1
 #'(lambda (file)
     (cond ((= *current-state* 1)
            (format t "User ~A getting file ~A~%" *current-user* file))
           (t
            (format t "Error: nobody logged in~%")))))

(add-transition 
 "QUIT" 0 0
 #'(lambda () 
     (cond ((= *current-state* 1)
            (format t "Ending session with ~A~%" *current-user*))
           (t
            (format t "Error: nobody logged in~%")))))

;;; test
(handle-command "HELLO MATT")
(handle-command "GET file.txt")
(handle-command "QUIT")


--Matthieu


Matthieu Villeneuve wrote:
> 
> How about having a "transition" structure having: start and end states
> (plain integers), label (the protocol commands), and a function
> evaluated when going through the transition?
> 
> The current state of the automaton has to be kept for each connection,
> along with other information, to maintain a kind of session.
> 
> When receiving a command, the server gets the corresponding transition,
> for example from a string-indexed hashtable, checks that the current
> state of the session is equal to the transition's start state, then
> evaluates the transition's function with the relevant arguments, and
> sets the current state to the transition's end state.
> 
> Following is some toy code playing with that idea, and with a simple
> protocol supporting only 3 commands HELLO, GET, QUIT (of course, much
> should be added to the code to check argument number, etc.)
> 
> (defstruct transition
>   start
>   end
>   label
>   function)
> 
> (defvar *atm* (make-hash-table :test 'equal))
> 
> (defvar *current-state* 0)
> 
> (defun add-transition (start end label function)
>   (let ((transition (make-transition :start start
>                                      :end end
>                                      :label label
>                                      :function function)))
>     (setf (gethash label *atm*) transition))
>   *atm*)
> 
> (defun handle-command (command)
>   (let* ((tokens (split-string command #\Space))
>          (label (first tokens))
>          (args (rest tokens))
>          (trans (gethash label *atm*)))
>     (when (null trans)
>       (error "Unknown command"))
>     (when (/= (transition-start trans) *current-state*)
>       (error "Incorrect state"))
>     (apply (transition-function trans) args)
>     (setf *current-state* (transition-end trans))))
> 
> (add-transition 0 1 "HELLO"
>                 #'(lambda (name) (format t "Start session with ~A~%"
> name)))
> 
> (add-transition 1 1 "GET"
>                 #'(lambda (file) (format t "Get file ~A~%" file)))
> 
> (add-transition 1 2 "QUIT"
>                 #'(lambda () (format t "Quit...~%")))
> 
> --Matthieu
> 
> Frederic Brunel wrote:
> >
> > Hi dudes,
> >
> > I would like to have your opinion about the implementation of a simple
> > network protocol with Common Lisp. I've done a TFTP (RFC 1350)
> > implementation in Java for my job and I've faced some design issues.
> > Since the TFTP protocol could be easily modeled by an automata, my
> > idea is that I could implement it using a finite state machine with
> > Common Lisp which seems perfectly suited for this kind of job!
> >
> > Do you think it's a good solution for implementing this protocol and
> > do you have any advices for doing it?
> >
> > --
> > Frederic Brunel
From: Frederic Brunel
Subject: Re: Implementing a simple network protocol
Date: 
Message-ID: <3c8290be$0$8733$626a54ce@news.free.fr>
> (defstruct transition
>   label
>   function
>   arg-count-min
>   arg-count-max)
>
> (defvar *atm* (make-hash-table :test 'equal))
>
> (defvar *current-state* 0)
> (defvar *current-user* nil)
>
> (defun add-transition (label arg-count-min arg-count-max function)
>   (let ((transition (make-transition :label label
>                                      :function function
>                                      :arg-count-min arg-count-min
>                                      :arg-count-max arg-count-max)))
>     (setf (gethash label *atm*) transition))
>   *atm*)
>
> (defun handle-command (command)
>   (let* ((tokens (split-string command #\Space))
>          (label (first tokens))
>          (args (rest tokens))
>          (trans (gethash label *atm*)))
>     (when (null trans)
>       (error "Unknown command"))
>     (when (not (<= (transition-arg-count-min trans)
>                    (length args)
>                    (transition-arg-count-max trans)))
>       (error "Incorrect argument number"))
>     (apply (transition-function trans) args)))
>
> (add-transition
>  "HELLO" 1 1
>  #'(lambda (name)
>      (cond ((= *current-state* 0)
>             (setf *current-user* name)
>             (format t "Starting session with ~A~%" name)
>             (setf *current-state* 1))
>            (t
>             (format t "Error: another user already logged in~%")))))
>
> (add-transition
>  "GET" 1 1
>  #'(lambda (file)
>      (cond ((= *current-state* 1)
>             (format t "User ~A getting file ~A~%" *current-user* file))
>            (t
>             (format t "Error: nobody logged in~%")))))
>
> (add-transition
>  "QUIT" 0 0
>  #'(lambda ()
>      (cond ((= *current-state* 1)
>             (format t "Ending session with ~A~%" *current-user*))
>            (t
>             (format t "Error: nobody logged in~%")))))
>
> ;;; test
> (handle-command "HELLO MATT")
> (handle-command "GET file.txt")
> (handle-command "QUIT")
>
>
> --Matthieu

Thanx Matthieu for your suggestion... But maybe there is something quite
simple to hard-code the state machine? Use a general one is maybe too
compilated for the requirements of a protcol like TFTP!
From: Nils Goesche
Subject: Re: Implementing a simple network protocol
Date: 
Message-ID: <a5jmbq$7nbda$2@ID-125440.news.dfncis.de>
In article <·························@news.free.fr>, Frederic Brunel wrote:
> I would like to have your opinion about the implementation of a simple
> network protocol with Common Lisp. I've done a TFTP (RFC 1350)
> implementation in Java for my job and I've faced some design issues.
> Since the TFTP protocol could be easily modeled by an automata, my
> idea is that I could implement it using a finite state machine with
> Common Lisp which seems perfectly suited for this kind of job!
> 
> Do you think it's a good solution for implementing this protocol and
> do you have any advices for doing it?

May I ask why it has to be TFTP of all things?  The point of TFTP
was that it is easy to implement in Assembler, without any protocol
stack or even a working network device driver at all (for bootstrapping
embedded systems etc.).  A much better way to check out whether a
language excels at networking stuff is how hard it is to implement
some higher layer protocols.  Look how Q.931 is written in C-like
languages, and see what you can do about that in Lisp.  That's
where the language becomes interesting.  IMHO, of course.

Regards,
-- 
Nils Goesche
Ask not for whom the <CONTROL-G> tolls.

PGP key ID 0xC66D6E6F
From: Frederic Brunel
Subject: Re: Implementing a simple network protocol
Date: 
Message-ID: <3c829297$0$8717$626a54ce@news.free.fr>
> May I ask why it has to be TFTP of all things?  The point of TFTP
> was that it is easy to implement in Assembler, without any protocol
> stack or even a working network device driver at all (for bootstrapping
> embedded systems etc.).  A much better way to check out whether a
> language excels at networking stuff is how hard it is to implement
> some higher layer protocols.  Look how Q.931 is written in C-like
> languages, and see what you can do about that in Lisp.  That's
> where the language becomes interesting.  IMHO, of course.

Because I have some experience with its implementation in Java...
I work for a company which use it to download applications to mobile
phones! I've faced some problems when implementing it in Java because
if I want to do it quite well, I have to deploy complex software
engineering patterns like the "State Pattern". I am not happy because
it requires too much work and implementing the protocol in a hard-coded
class it really ugly and unmaintainable!