\ignore{ \begin{code}
{-#OPTIONS -cpp #-}
{-|
Module      :  Parry.Client
Copyright   :  (c) Pierre-Étienne Meunier 2014
License     :  GPL-3
Maintainer  :  pierre-etienne.meunier@lif.univ-mrs.fr
Stability   :  experimental
Portability :  All

Tool to build clients for Parry.
-}
\end{code} } We now prove the client functions. More precisely, we prove that the clients that can be built using the functions in this module are valid and fluent, provided that their ``worker'' function explores the space correctly. We include the whole source code of this module for the sake of completeness. \hfill \begin{code}
module Parry.Client(
  -- * Jobs on the client side
  Client(..),
  -- * Writing clients
  client,Config(..),defaultConfig
  ) where
import Network
import System.IO
import System.Exit
#ifdef UNIX
import System.Posix.Signals
#endif
import Control.Concurrent
import Control.Exception as E
import Data.List
import Codec.Crypto.RSA.Pure
import qualified Data.ByteString.Lazy.Char8 as B8
import qualified Data.ByteString.Char8 as S
import Data.Binary
import Parry.Util
import Parry.Protocol


-- | For a job to be usable by Parry's clients, it must be a member of this class.
class Client j where
  -- | This function is used by the client to distinguish results from regular jobs.
  isResult::j->Bool
\end{code} \ignore{ \begin{code}
-- | Client configuration
data Config=Config {
  -- | The server's host name (either a DNS name or an IP address).
  server::String,
  -- | The port number (for instance @'Network.PortNumber' 5129@).
  port::PortID,
  -- | The private key to sign messages.
  privateKey::PrivateKey,
  -- | The public key, that the server must know of before the clients connect.
  publicKey::PublicKey
  }

-- | A default configuration, matching the server's
-- 'Parry.Server.defaultConfig'.  Note that, like in the server, you
-- must provide your own private key for signing the protocol
-- messages. See 'Parry.Server.defaultConfig' for an example method to
-- generate these keys.
defaultConfig::PrivateKey->PublicKey->Config
defaultConfig pr pu=Config {
  server="127.0.0.1",
  port=PortNumber 5129,
  privateKey=pr,
  publicKey=pu
  }
\end{code} } \begin{lemma} \label{lem:signAndSend} The {\tt signAndSend\_} and {\tt signAndSend} functions send only one kind of messages on the network, with two lines: the first line is an encoding via {\tt encode16l} of $m$, the encoding via {\tt encode} of a constructor of the {\tt ClientMessage} type. The second line is the RSA signature of $m$ using the private key in the {\tt conf} argument. \begin{proof} The results follows from the fact that this function uses only two lines to send messages on the network, and these two lines have the claimed form. \hfill \begin{code}
-- | A wrapper around signAndSend, to force the type of @j@, which is
-- needed for the protocol's @Alive@ messages.
signAndSend_::(Binary j)=>Config->j->ClientMessage j->IO (ServerMessage j)
signAndSend_ conf _ a=signAndSend conf a

-- | Sign a message and send it.
signAndSend::(Binary j)=>Config->ClientMessage j->IO (ServerMessage j)
signAndSend conf m=do {
  let { msg=encode m };
  case sign (privateKey conf) msg of {
    Right b->E.catch (do {
                         l<-bracket (connectTo (server conf) (port conf)) (hClose)
                            (\h->do {
                                B8.hPutStrLn h $ encode16l msg;
                                B8.hPutStrLn h $ encode16l b;
                                hFlush h;
                                S.hGetLine h;
                                });
                         case decodeOrFail $ decode16l $ B8.fromStrict l of {
                           Right (_,_,a)->return a;
                           Left _->do {
                             threadDelay 1000000;
                             signAndSend conf m
                             }
                           }})
             (\e->do {
                 let { _=e::IOError };
                 print e;
                 threadDelay 1000000;signAndSend conf m
                 });
    er->do {
      print er;hFlush stdout;threadDelay 100000;signAndSend conf m
      }
    }}
\end{code} \end{proof} \end{lemma} To prove the remaining functions, we need to introduce the following invariant on their arguments: \begin{invariant} \label{h} When the {\tt cur} variable is not {\tt Nothing}, the {\tt jobs} and {\tt results} variables contain, respectively, the list of all jobs of the contents of {\tt cur} that have not been completely explored, and the list of results found during the exploration of all other subjobs of the job in {\tt cur}. \end{invariant} \begin{lemma} \label{lem:dowork} If there is a function {\tt doWork} such that, at the same time: \begin{enumerate} \item For all values of {\tt b}, {\tt save} and {\tt j}, {\tt doWork b save j} only calls {\tt save} with arguments {\tt l} and {\tt r}, where {\tt r} is the list of all results that have been found when the subjobs of {\tt j} that have not been completely explored are all in list {\tt l}. \item For all values of {\tt b}, {\tt save} and {\tt j}, {\tt doWork b save j} returns the list of all subjobs of {\tt j} that have not been explored, and all results that have been found in {\tt j}, in the remaining subjobs of {\tt j}. \end{enumerate} Then {\tt client conf doWork} is a valid and fluent client. \begin{proof} We first prove fluency: in the two functions below ({\tt work} and {\tt client}), the only messages sent to the network are either sent using {\tt signAndSend}, or else consist of a single line containing exactly {\tt Hello}, which is the definition of fluency (by Lemma \ref{lem:signAndSend}). We now prove that {\tt client conf doWork} is valid. Indeed, condition \ref{valid:newjobs} of validity ({\tt NewJobs} messages contain all subjobs of the current job) is clearly respected by the {\tt work} function: indeed, by the definition of a result, the {\tt todo} variable in {\tt work} is the list of all subjobs that have not been explored. Condition \ref{valid:jobdone} is also respected, because it is not applicable to the {\tt work} function. Moreover, the {\tt work} function returns either {\tt Nothing}, or {\tt Just (j,r)} where {\tt j} is a job, and {\tt r} is the list of all results found during the exploration of {\tt j}. We prove this recursively on its code: \hfill \begin{code}
work::(Client j,Binary j)=>Config->MVar [j]->MVar[j]->MVar (Maybe j)->
      (Bool->([j]->[j]->IO ())->j->IO [j])->Bool->Integer->j->IO (Maybe (j,[j]))
work conf jobs results cur doWork shared num j=do
  let save jobs_ results_ jobs results=do
        modifyMVar_ jobs_ $ \_->return jobs
        modifyMVar_ results_ $ \_->return results
  someWork<-doWork shared (save jobs results) j
  let (result,todo)=partition isResult someWork
  case todo of
\end{code} \hfill Either {\tt doWork} has returned no new jobs, in which case the induction hypothesis clearly holds, because the exploration of the current job is over. \hfill \begin{code}
    []->return (Just (j,result))
\end{code} \hfill Or {\tt doWork} returns some new jobs. In this case, {\tt work} is called on {\tt u} only after reply {\tt Ack} has been received from the server, acknowledging that the current job registered for this client has been updated. Therefore, {\tt work} is called recursively on the current job of this client, and hence, the claim also holds, by recursion. \hfill \begin{code}
    u:v->do
      x<-signAndSend conf (NewJobs {clientId=num,jobResults=result,
                                    currentJob=j,nextJob=u,newJobs=v })
      modifyMVar_ cur (\_->do {
                          modifyMVar_ results (\_->return []);
                          modifyMVar_ jobs (\_->return [u]);
                          return (Just u)
                          })
      case x of
        Ack->work conf jobs results cur doWork (shared && (null v)) num u;
        _->return Nothing
\end{code} \hfill We will now prove validity for the code of the {\tt client} function. We have assumed that the {\tt save} function above is only called on a list {\tt l} of subjobs, and a list {\tt r} of results, such that {\tt l} contains all subjobs of the initial job {\tt j} that have not been completely explored when results {\tt r} are found. Therefore, in the {\tt work} function above, Invariant \ref{h} on the {\tt jobs\_} and {\tt results\_} variables is clearly maintained: \hfill \ignore{ \begin{code}
-- | @'client' config work@ calls its argument function @work@ on a
-- boolean @s@ (if the server asked to share jobs) and the actual job
-- @j@ that must be done. @work@ must return a list of resulting jobs,
-- that may include results (see 'Parry.Protocol.Client').
--
-- Workers asked to share their input job should share it as early as
-- possible: this usually means that the job already got killed in a
-- previous attempt, probably because of its length.
\end{code} } \begin{code}
client::(Binary j,Client j)=>
        Config->(Bool->([j]->[j]->IO())->j->IO [j])->IO ()
client conf doWork=
  let startConnection=do
        hPutStrLn stderr "Connecting..."
        hFlush stderr
        l<-E.catch (bracket (connectTo (server conf) (port conf)) hClose
                    (\h->do {
                        B8.hPutStrLn h (B8.pack "Hello");
                        hFlush h;
                        l<-S.hGetLine h;
                        case decodeOrFail (decode16l (B8.fromStrict l)) of {
                          Right x->return (Right x);
                          Left _->return (Left ())
                          }}))
           (\e->let _=e::IOError in do { hPutStrLn stderr $ show e; return (Left ()) })
        case l of
          Right (_,_,num)->do
            jobs<-newMVar []
            results<-newMVar []
            cur<-newMVar Nothing
\end{code} \hfill At this point, {\tt cur} contains {\tt Nothing}: Invariant \ref{h} is clearly maintained. Now remark that, when the {\tt saveAll} function below is called, and the {\tt cur} variable contains {\tt Nothing}, no message is sent. Therefore, if Invariant \ref{h} holds when {\tt saveAll} is called, the corresponding {\tt NewJobs} message respects the validity condition. \hfill \begin{code}
            let saveAll=withMVar jobs $ \j->
                  withMVar cur $ \curj->
                  case (j,curj) of {
                    (h:s,Just cu)->
                       withMVar results $ \res->do {
                         _<-signAndSend conf (
                            NewJobs { clientId=num,
                                      jobResults=res,
                                      currentJob=cu,
                                      nextJob=h,
                                      newJobs=s
                                    });
                         return ()};
                       _->return ()
                    }
            myth<-myThreadId
            threads<-newMVar myth
#ifdef UNIX
            installHandler sigTERM
              (Catch (modifyMVar_ threads (\a->do {if a==myth then return () else
                                                     killThread a;return myth}))) Nothing
#endif
            let getAJob=do
                  job_<-signAndSend conf (GetJob num (publicKey conf));
                  case job_ of
                    Job share j->do
                      modifyMVar_ cur (\_->do {
                                          modifyMVar_ jobs (\_->return [j]);
                                          modifyMVar_ results (\_->return []);
                                          return (Just j)
                                          });
                      workerMVar<-newEmptyMVar;
\end{code} \hfill At this point, Invariant \ref{h} still holds: the only subjob of the current job is itself, and no results have been found. \hfill \begin{code}
                      workerThread<-
                        forkFinally
                        (do {
                            x<-work conf jobs results cur doWork share num j;
                            case x of {
                              Just (j',r)->do {
                                 _<-signAndSend conf
                                    (JobDone {clientId=num,
                                              currentJob=j',
                                              jobResults=r });
                                 return () };
                              Nothing->return ()
                              }})
                        (\_->putMVar workerMVar ())
\end{code} \hfill Here, the validity condition holds for the {\tt JobDone} message: indeed, the current job has been completely explored, and found exactly the results in {\tt r}. \hfill \begin{code}
                      let heartbeat=do
                            answer<-signAndSend_ conf j (Alive num)
                            case answer of
                              Ack->do { threadDelay 300000000; heartbeat }
                              _->do { saveAll; killThread workerThread }
                      heartbeatMVar<-newEmptyMVar
                      hbThread<-forkIO
                                (heartbeat`finally`(putMVar heartbeatMVar ()))
                      modifyMVar_ threads (\_->return workerThread)
                      takeMVar workerMVar
                      killThread hbThread
                      takeMVar heartbeatMVar
                      stop<-withMVar threads (\t->return (t==myth))
                      if stop then do { saveAll; exitSuccess } else getAJob
                    Finished->return ();
                    _->do
                      threadDelay 10000000
                      getAJob
            getAJob
          Left _->do
            threadDelay 5000000
            startConnection
  in
   startConnection
\end{code} \end{proof} \end{lemma}