lthms' avatar, a hand drawing looking person, wearing a headset, close to a window on a raining night
Thomas Letan
lthms · he/him

Did you come across something which caught your attention? Don’t hesitate to shoot me an email in my public inbox.

 Published on  Modified on

Implementing an Echo Server in Coq with coqffi.1.0.0

In this article, we will demonstrate how coqffi can be used to implement an echo server, i.e., a TCP server which sends back any input it receives from its clients. In addition to coqffi, you will need to install coq-simple-io. The latter is available in the released repository of the Opam Coq Archive .

opam install coq-coqffi coq-simple-io

Besides, you can download the source tree presented in this article if you want to try to read the source directly, or modify it to your taste.

Project Layout

Before diving too much into the implementation of our echo server, we first give an overview of the resulting project’s layout. Since we aim at implementing a program, we draw our inspiration from the idiomatic way of organizing a OCaml project.

We have three directories at the root of the project.

Note that we could have decided to only have one Coq theory. We could also have added a fourth directory (theories/) for formal verification specific code, but this is out of the scope of this tutorial.

Overall, we use dune to compile and compose the different parts of the echo server. dune has a native —yet unstable at the time of writing— support for building Coq projects, with very convenient stanzas like coq.theory and coq.extraction.

The following graph summarizes the dependencies between each component (plain arrows symbolize software dependencies).

The echo server dependy graph

The echo server dependency graph. Dashed boxes are generated.

We enable Coq-related stanza with (using coq 0.2) in the dune-project. file.

(lang dune 2.7)
(using coq 0.2)

The rest of this tutorial proceeds by diving into each directory.

FFI Bindings

Our objective is to implement an echo server, i.e., a server which (1) accepts incoming connections, and (2) sends back any incoming messages. We will consider two classes of effects. One is related to creating and manipulating TCP sockets. The other is dedicated to process management, more precisely to be able to fork when receiving incoming connections.

Therefore, the ffi library will provide two modules. Likewise, the FFI theory will provide two analogous modules generated by coqffi.

In the ffi/ directory, we add the following stanza to the dune file.

(library
  (name ffi)
  (libraries unix))

dune will look for any .ml and .mli files within the directory and will consider they belong to the ffi library. We use the unix  library to implement the features we are looking for.

Then, we add the following stanza to the dune file of the ffi/ directory.

(coq.theory
  (name FFI))

This tells dune to look for .v file within the ffi/ directory, in order to build them with Coq. A nice feature of dune is that if we automatically generate Coq files, they will be automatically “attached” to this theory.

Sockets

Sockets are boring. The following OCaml module interface provides the necessary type and functions to manipulate them.

type socket_descr

val open_socket : string -> int -> socket_descr
val listen : socket_descr -> unit
val recv : socket_descr -> string
val send : socket_descr -> string -> int
val accept_connection : socket_descr -> socket_descr
val close_socket : socket_descr -> unit

Our focus is how to write the interface modules for coqffi. Since the object of this tutorial is not the implementation of an echo server in itself, the implementation details of the ffi library will not be discussed, but is provided at the end of this article.

dune generates .cmi files for the .mli files of our library, and provides the necessary bits to easily locate them. Besides, the action stanza can be used here to tell to dune how to generate the module Socket.v from file.cmi. We add the following entry to ffi/dune.

(rule
  (target Socket.v)
  (action (run coqffi %{cmi:socket} -o %{target})))

We call coqffi without any feature-related command-line argument, which means only the simple-io feature is enabled. As a consequence, the socket_descr type is axiomatized in Coq, and in addition to a MonadSocket monad, coqffi will generate an instance for this monad for the IO monad of coq-simple-io.

The stanza generates the following Coq module.

(* This file has been generated by coqffi. *)

Set Implicit Arguments.
Unset Strict Implicit.
Set Contextual Implicit.
Generalizable All Variables.
Close Scope nat_scope.

From CoqFFI Require Export Extraction.
From SimpleIO Require Import IO_Monad.

Axiom socket_descr : Type.

Extract Constant socket_descr => "Ffi.Socket.socket_descr".

(** * Impure Primitives *)

(** ** Monad Definition *)

Class MonadSocket (m : Type -> Type) : Type :=
  { open_socket : string -> i63 -> m socket_descr
  ; listen : socket_descr -> m unit
  ; recv : socket_descr -> m string
  ; send : socket_descr -> string -> m i63
  ; accept_connection : socket_descr -> m socket_descr
  ; close_socket : socket_descr -> m unit
  }.

(** ** [IO] Instance *)

Axiom io_open_socket : string -> i63 -> IO socket_descr.
Axiom io_listen : socket_descr -> IO unit.
Axiom io_recv : socket_descr -> IO string.
Axiom io_send : socket_descr -> string -> IO i63.
Axiom io_accept_connection : socket_descr -> IO socket_descr.
Axiom io_close_socket : socket_descr -> IO unit.

Extract Constant io_open_socket
  => "(fun x1 x2 k__ -> k__ ((Ffi.Socket.open_socket x1 x2)))".
Extract Constant io_listen => "(fun x1 k__ -> k__ ((Ffi.Socket.listen x1)))".
Extract Constant io_recv => "(fun x1 k__ -> k__ ((Ffi.Socket.recv x1)))".
Extract Constant io_send
  => "(fun x1 x2 k__ -> k__ ((Ffi.Socket.send x1 x2)))".
Extract Constant io_accept_connection
  => "(fun x1 k__ -> k__ ((Ffi.Socket.accept_connection x1)))".
Extract Constant io_close_socket
  => "(fun x1 k__ -> k__ ((Ffi.Socket.close_socket x1)))".

Instance IO_MonadSocket : MonadSocket IO :=
  { open_socket := io_open_socket
  ; listen := io_listen
  ; recv := io_recv
  ; send := io_send
  ; accept_connection := io_accept_connection
  ; close_socket := io_close_socket
  }.

(* The generated file ends here. *)

Process Management

In order to avoid a client to block the server by connecting to it without sending anything, we can fork a new process for each client.

type identity = Parent of int | Child

val fork : unit -> identity

This time, the proc.mli module interface introduces a transparent type, /i.e./, it also provides its definition. This is a good use case for the transparent-types feature of coqffi. In the stanza for generating Proc.v, we enable it with the -ftransparent-types command-line argument, like this.

(rule
  (target Proc.v)
  (action (run coqffi -ftransparent-types %{cmi:proc} -o %{target})))

which generates the following Coq module.

(* This file has been generated by coqffi. *)

Set Implicit Arguments.
Unset Strict Implicit.
Set Contextual Implicit.
Generalizable All Variables.
Close Scope nat_scope.

From CoqFFI Require Export Extraction.
From SimpleIO Require Import IO_Monad.

Inductive identity : Type :=
| Parent (x0 : i63) : identity
| Child : identity.

Extract Inductive identity => "Ffi.Proc.identity"
  [ "Ffi.Proc.Parent" "Ffi.Proc.Child" ].

(** * Impure Primitives *)

(** ** Monad Definition *)

Class MonadProc (m : Type -> Type) : Type := { fork : unit -> m identity
                                             }.

(** ** [IO] Instance *)

Axiom io_fork : unit -> IO identity.

Extract Constant io_fork => "(fun x1 k__ -> k__ ((Ffi.Proc.fork x1)))".

Instance IO_MonadProc : MonadProc IO := { fork := io_fork
                                        }.

(* The generated file ends here. *)

We now have everything we need to implement an echo server in Coq.

Implementing an Echo Server

Our implementation will be part of a dedicated Coq theory, called Echo. This is done easily a dune file in the src/ directory, with the following content.

(coq.theory
  (name Echo)
  (theories FFI))

In the rest of this section, we will discuss the content of the unique module of this theory. Hopefully, readers familiar with programming impurity by means of monads will not find anything particularly surprising here.

Let us start with the inevitable sequence of import commands. We use the Monad and MonadFix typeclasses of ExtLib, and our FFI modules from the FFI theory we have previously defined.

From ExtLib Require Import Monad MonadFix.
From FFI Require Import Proc Socket.

Letting Coq guess the type of unintroduced variables using the ` annotation (e.g., in presence of `{Monad m}, Coq understands m is of type Type -> Type) is always nice, so we enable it.

Generalizable All Variables.

We enable the monad notation provided by ExtLib. In this article, we prefer the let* notation (as recently introduced by OCaml) over the <- notation of Haskell, but both are available.

Import MonadLetNotation.
Open Scope monad_scope.

Then, we define a notation to be able to define local, monadic recursive functions using the mfix combinator of the MonadFix typeclass.

Notation "'let_rec*' f x ':`' p 'in' q" :`
  (let f :` mfix (fun f x `> p) in q)
    (at level 61, x pattern, f name, q at next level, right associativity).

Note that mfix does /not/ check whether or not the defined function will terminate (contrary to the fix keyword of Coq). This is fortunate because in our case, we do not want our echo server to converge, but rather to accept an infinite number of connections.

We can demonstrate how this notation can be leveraged by defining a generic TCP server, parameterized by a handler to deal with incoming connections.

Definition tcp_srv `{Monad m, MonadFix m, MonadProc m, MonadSocket m}
    (handler : socket_descr -> m unit)
  : m unit :=
  let* srv := open_socket "127.0.0.1" 8888 in
  listen srv;;

  let_rec* tcp_aux _ :=
    let* client := accept_connection srv in
    let* res := fork tt in
    match res with
    | Parent _ => close_socket client >>= tcp_aux
    | Child =>  handler client
    end
  in

  tcp_aux tt.

The handler for the echo server is straightforward: it just reads incoming bytes from the socket, sends it back, and closes the socket.

Definition echo_handler `{Monad m, MonadSocket m} (sock : socket_descr)
  : m unit :=
  let* msg := recv sock in
  send sock msg;;
  close_socket sock.

Composing our generic TCP server with our echo handler gives us an echo server.

Definition echo_server `{Monad m, MonadFix m, MonadProc m, MonadSocket m}
  : m unit :=
  tcp_srv echo_handler.

Because coqffi has generated typeclasses for the impure primitives of proc.mli and socket.mli, echo_server is polymorphic, and can be instantiated for different monads. When it comes to extracting our program, we will generally prefer the IO monad of coq-simple-io. But we could also imagine verifying the client handler with FreeSpec, or the generic TCP server with Interaction Trees (which support diverging computations). Overall, we can have different verification strategies for different parts of our program, by leveraging the most relevant framework for each part, yet being able to extract it in an efficient form.

The next section shows how this last part is achieved using, once again, a convenient stanza of dune.

Extracting and Building an Executable

The 0.2 version of the Coq-related stanzas of dune provides the coq.extraction stanza, which can be used to build a Coq module expected to generate ml files.

In our case, we will write bin/echo.v to extract the echo_server in a echo.ml module, and uses the executable stanza of dune to get an executable from this file. To achieve this, the bin/dune file simply requires these two stanzas.

(coq.extraction
  (prelude echo)
  (theories Echo)
  (extracted_modules echo))

(executable
  (name echo)
  (libraries ffi))

We are almost done. We now need to write the echo.v module, which mostly consists of (1) providing a MonadFix instance for the IO monad, (2) using the IO.unsafe_run function to escape the IO monad, (3) calling the Extraction command to wrap it up.

From Coq Require Extraction.
From ExtLib Require Import MonadFix.
From SimpleIO Require Import SimpleIO.
From Echo Require Import Server.

Instance MonadFix_IO : MonadFix IO :=
  { mfix := @IO.fix_io }.

Definition main : io_unit :=
  IO.unsafe_run echo_server.

Extraction "echo.ml" main.

Since we are using the i63 type (signed 63bits integers) of the CoqFFI theory, and since i63 is implemented under the hood with Coq primitive integers, we also need to provide a Uint63 module with a of_int function. Fortunately, this module is straightforward to write.

let of_int x = x

And voilà. A call to dune at the root of the repository will build everything (Coq and OCaml alike). Starting the echo server is as simple as

dune exec bin/echo.exe

And connecting to it can be achieved with a program like telnet.

$ telnet 127.0.0.1 8888
Trying 127.0.0.1...
Connected to 127.0.0.1.
Escape character is '^]'.
hello, echo server!
hello, echo server!
Connection closed by foreign host.

Appendix

The Socket OCaml Module

There is not much to say, except that (as already stated) we use the Unix module to manipulate sockets, and we attach to each socket a buffer to store incoming bytes.

let buffer_size = 1024

type socket_descr = {
  fd : Unix.file_descr;
  recv_buffer : bytes;
}

let from_fd fd =
  let rbuff = Bytes.create buffer_size in
  { fd ` fd; recv_buffer ` rbuff }

let open_socket hostname port =
  let open Unix in
  let addr = inet_addr_of_string hostname in
  let fd = socket PF_INET SOCK_STREAM 0 in
  setsockopt fd SO_REUSEADDR true;
  bind fd (ADDR_INET (addr, port));
  from_fd fd

let listen sock = Unix.listen sock.fd 1

let recv sock =
  let s = Unix.read sock.fd sock.recv_buffer 0 buffer_size in
  Bytes.sub_string sock.recv_buffer 0 s

let send sock msg =
  Unix.write_substring sock.fd msg 0 (String.length msg)

let accept_connection sock =
  Unix.accept sock.fd |> fst |> from_fd

let close_socket sock = Unix.close sock.fd

The Proc OCaml Module

Thanks to the Unix module, the implementation is pretty straightforward.

type identity = Parent of int | Child

let fork x =
  match Unix.fork x with
  | 0 -> Child
  | x -> Parent x