packages feed

alms-0.6.0: lib/libsocketcap.alms

(*
    A typestate sockets library

    This is a bit more involved than the example in the paper,
    because we have error cases.  We deal with this by raising
    an exception which contains a witness that allows recovering
    the capability if presented with the corresponding socket.
*)

#load "libsocket"

module type ASOCKET = sig
  (* The representation of a socket *)
  type 't socket

  (* The socket states *)
  type 't initial   : A
  type 't bound     : A
  type 't listening : A
  type 't connected : A

  (* Socket operations *)
  val socket  : unit → ∃ 't. 't socket × 't initial
  val bind    : 't socket → int → 't initial → 't bound
  val connect : 't socket → string → string →
                  't initial + 't bound → 't connected
  val listen  : 't socket → 't bound → 't listening
  val accept  : 't socket → 't listening →
                  (∃ 's. 's socket × 's connected) × 't listening
  val send    : 't socket → string →
                  't connected → 't connected
  val recv    : 't socket → int →
                  't connected → string × 't connected
  val close   : 't socket → 't connected → unit

  (* When we raise an exception, we "freeze" the capability.
   * We can thaw the frozen capability if we have the socket that
   * it goes with.  (This requires a dynamic check.)  This lets us
   * recover the capability with a type paramater that matches any
   * extant sockets that go with it. *)
  type frozenInitial   : A
  type frozenBound     : A
  type frozenListening : A
  type frozenConnected : A

  (* Operations for reassociating frozen capabilities with their
     sockets. *)
  val thawInitial   : 't socket → frozenInitial →
                        frozenInitial + 't initial
  val thawBound     : 't socket → frozenBound →
                        frozenBound + 't bound
  val thawListening : 't socket → frozenListening →
                        frozenListening + 't listening
  val thawConnected : 't socket → frozenConnected →
                        frozenConnected + 't connected

  (* Operations for catching the error state associated with a given
     socket. *)
  val catchInitial   : 't socket →
                         (unit -o `a) → ('t initial -o `a) -o `a
  val catchBound     : 't socket →
                         (unit -o `a) → ('t bound -o `a) -o `a
  val catchListening : 't socket →
                         (unit -o `a) → ('t listening -o `a) -o `a
  val catchConnected : 't socket →
                         (unit -o `a) → ('t connected -o `a) -o `a

  (* Socket exceptions *)
  exception SocketError    of string
  exception StillInitial   of frozenInitial × string
  exception StillBound     of frozenBound × string
  exception StillListening of frozenListening × string
  exception StillConnected of frozenConnected × string
end

module ASocket : ASOCKET = struct
  module S = Socket
  let getAddrByName = S.getAddrByName

  type rep        = S.socket
  type 't socket  = S.socket

  type 't initial   = unit
  type 't bound     = unit
  type 't listening = unit
  type 't connected = unit

  type frozenInitial   = rep
  type frozenBound     = rep
  type frozenListening = rep
  type frozenConnected = rep

  exception SocketError    of string
  exception StillInitial   of rep × string
  exception StillBound     of rep × string
  exception StillListening of rep × string
  exception StillConnected of rep × string

  let socket () : ∃'t. 't socket × 't initial =
    try (S.socket (), ())
    with
      IOError s → raise (SocketError s)

  let bind (sock: rep) (port: int) () =
    try S.bind sock port
    with
      IOError msg → raise (StillInitial (sock, msg))

  let connect (sock: rep) (host: string) (port: string)
              (cap: unit + unit) =
    try S.connect sock host port
    with
      IOError msg → match cap with
        | Left _  → raise (StillInitial (sock, msg))
        | Right _ → raise (StillBound (sock, msg))

  let listen (sock: rep) () =
    try S.listen sock
    with
      IOError msg → raise (StillBound (sock, msg))

  let accept (sock: rep) () =
    try ((S.accept sock, ()) : ∃'s. 's socket × 's connected, ())
    with
      IOError msg → raise (StillListening (sock, msg))

  let send (sock: rep) (data: string) () =
    try
      S.send sock data;
      ()
    with
      IOError msg → raise (SocketError msg)

  let recv (sock: rep) (len: int) () =
    try (S.recv sock len, ())
    with
      IOError msg → raise (SocketError msg)

  let close (sock: rep) () =
    try S.close sock
    with
      IOError msg → raise (SocketError msg)

  (* Convenience functions for catching and thawing frozen socket
   * capabilities. *)
  let thaw (sock: rep) (sock': rep) =
    if sock == sock'
      then Right ()
      else Left  sock'

  let thawInitial     = thaw
  let thawBound       = thaw
  let thawListening   = thaw
  let thawConnected   = thaw

  let catchInitial (sock: rep) (body: unit -o `a)
                   (handler: unit -o `a) =
    try body () with
    | StillInitial (frz, msg) →
        match thawInitial sock frz with
        | Left frz  → raise (StillInitial (frz, msg))
        | Right cap → handler cap

  let catchBound (sock: rep) (body: unit -o `a)
                 (handler: unit -o `a) =
    try body () with
    | StillBound (frz, msg) →
        match thawBound sock frz with
        | Left frz  → raise (StillBound (frz, msg))
        | Right cap → handler cap

  let catchListening (sock: rep) (body: unit -o `a)
                     (handler: unit -o `a) =
    try body () with
    | StillListening (frz, msg) →
        match thawListening sock frz with
        | Left frz  → raise (StillListening (frz, msg))
        | Right cap → handler cap

  let catchConnected (sock: rep) (body: unit -o `a)
                     (handler: unit -o `a) =
    try body () with
    | StillConnected (frz, msg) →
        match thawConnected sock frz with
        | Left frz  → raise (StillConnected (frz, msg))
        | Right cap → handler cap
end