packages feed

hMPC-0.1.0.0: app/Asyncoro.hs

{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE TypeApplications #-}

-- | This module provides basic support for asynchronous communication
-- and computation of secret-shared values.
module Asyncoro (createConnections, send, receive, Gather(..), async, asyncList, asyncListList, await, incPC, decreaseBarrier) where
import Network.Socket
import Network.Socket.ByteString (recv, sendAll)
import Control.Exception
import Control.Concurrent
import System.IO.Error
import Control.Monad
import Types
import Data.Function
import Data.List
import qualified Data.Map.Strict as Map
import qualified Data.Serialize as Enc
import qualified Data.ByteString as BS
import Data.Hashable
import Control.Monad.State
import SecTypes
import FinFields
import System.Log.Logger
import Text.Printf
import Parser


-- | Open connections with other parties, if any.
createConnections :: Int -> [Party] -> IO [Party]
createConnections myPid parties = do
    let m = length parties
    let listenPort = port $ parties !! myPid
    sock <- socket AF_INET Stream 0    -- create socket
    setSocketOption sock ReuseAddr 1   -- make socket immediately reusable - eases debugging.
    bind sock (SockAddrInet (fromIntegral listenPort) 0)   -- listen on TCP port 4242 + pid.
    listen sock 1                             -- set a max of 2 queued connections 
    serverParties <- replicateM myPid $ do
        mvar <- newEmptyMVar
        forkIO $ connectServer sock parties mvar
        return mvar
    
    clientParties <- forM (drop (myPid+1) parties) $ \party -> do
      mvar <- newEmptyMVar
      forkIO $ connectClient myPid party mvar
      return mvar

    channels <- mapM takeMVar (serverParties ++ clientParties)
    logging INFO $ printf "All %d parties connected." m

    close sock

    -- necessary if --single-threaded bug
    cap <- getNumCapabilities
    logging INFO $ printf "All threads run on %d cores." cap
    when (cap <= 1) (threadDelay 5000000)
    
    newDict <- newMVar Map.empty
    newMVar <- newMVar 0
    emptyChan <- newChan
    let selfParty = (parties !! myPid){outChan = emptyChan, sock = Nothing, dict = newDict, nbytesSent=newMVar}
    return $ sortBy (compare `on` pid) (selfParty: channels)

  where 
    connectServer sock parties mvar = do
      (conn, _) <- accept sock     -- accept a connection and handle it
      msg <- recv conn 1024 -- receive pid
      case Enc.decode msg of
        Right peer_pid -> initConnection conn (parties !! peer_pid) mvar

    connectClient pid peer mvar = do
      addr <- head <$> getAddrInfo Nothing (Just (host peer)) (Just $ show (port peer))
      sock <- openSocket addr
      res <- try @IOException $ connect sock (addrAddress addr)
      case res of
        Right _ -> do  -- connection successful
          sendAll sock (Enc.encode pid) --send pid
          initConnection sock peer mvar
        Left _ -> do  -- exception
          threadDelay 100000
          connectClient pid peer mvar

    initConnection sock peer mvar = do
      newDict <- newMVar Map.empty
      bytesMvar <- newMVar 1 -- one for the runSession to complete
      outChan <- newChan
      putMVar mvar peer{outChan = outChan, sock = Just sock, dict = newDict, nbytesSent=bytesMvar}
      runConnection outChan sock newDict bytesMvar


-- read lines from the socket and insert into dictionary
runConnection :: Chan BS.ByteString -> Socket -> MVar Dict -> MVar Int -> IO ()
runConnection chan sock dictMvar nbytesSent = do
    reader <- forkIO $ forever $ do
        dataToSend <- readChan chan
        sendAll sock dataToSend

    handle (\(SomeException _) -> return ()) $ flip fix BS.empty $ \loop buffer_old ->
      BS.append buffer_old <$> recv sock 1024 
        >>= decodeMessageChecks dictMvar nbytesSent 
        >>= loop
        >> return ()
    
    killThread reader

    where 
      decodeMessageChecks dictMvar nbytesSent buffer = do
        let bufferLength = BS.length buffer
        if bufferLength < 4
          then return buffer
          else do
            let payload_length = _decode Enc.getInt32le (BS.take 4 buffer)
                len_packet = payload_length + 12
            if bufferLength < len_packet
              then return buffer
              else decodeMessage buffer len_packet

      decodeMessage buffer len_packet = do
        let (msg, leftover) = BS.splitAt len_packet buffer
            pc = _decode Enc.getInt64le (BS.drop 4 buffer)
        modifyMVar_ nbytesSent (return . (+ len_packet))
        modifyMVar_ dictMvar $ \dict ->
          case Map.lookup pc dict of
            Just mvar -> do
              putMVar mvar (BS.drop 12 msg)
              return $ Map.delete pc dict
            Nothing -> do
              mvar <- newMVar (BS.drop 12 msg)
              return $ Map.insert pc mvar dict                                                    
        decodeMessageChecks dictMvar nbytesSent leftover

      _decode decoder buffer = 
        case Enc.runGet (liftM fromIntegral decoder) buffer of
          Right t -> t

-- | Receive payload labeled with given pc from the peer.
receive :: Int -> Party -> SIO (MVar BS.ByteString)
receive pc party = liftIO $ modifyMVar (dict party) $ \dict -> do
    case Map.lookup pc dict of
        Just value -> return (Map.delete pc dict, value)
        Nothing -> do
            mvar <- newEmptyMVar
            return ((Map.insert pc mvar dict), mvar)

-- | Transform 'SecureTypes' into 'FiniteField' by reading the future 'MVar' share that contains a 'FiniteField' (blocking).
class Gather a where
  type Result a :: *
  gather :: a -> SIO (Result a)

instance Gather SecureTypes where
  type Result SecureTypes = FiniteField
  gather = await . share

instance Gather a => Gather [a] where
  type Result [a] = [Result a]
  gather = mapM gather

instance (Gather a, Gather b) => Gather (a, b) where
  type Result (a, b) = (Result a, Result b)
  gather (x, y) = do
    resultX <- gather x
    resultY <- gather y
    return (resultX, resultY)

instance (Gather a, Gather b, Gather c) => Gather (a, b, c) where
  type Result (a, b, c) = (Result a, Result b, Result c)
  gather (x, y, z) = do
    resultX <- gather x
    resultY <- gather y
    resultZ <- gather z
    return (resultX, resultY, resultZ)

-- | Read the value from the future MVar (blocking).
await :: MVar a -> SIO a
await = liftIO . readMVar


-- | Send payload labeled with pc to the peer.
--
-- Message format consists of three parts:
--
-- 1. pc (8 bytes signed int)
--
-- 2. payload_size (4 bytes unsigned int)
-- 
-- 3. payload (byte string of length payload_size).
send :: Int -> BS.ByteString -> Party -> SIO ()
send pc payload party = do
  let payload_size = (BS.length payload)
  let bytes = ((Enc.runPut ( 
        (Enc.putInt32le . fromIntegral) payload_size
        >> (Enc.putInt64le . fromIntegral) pc)) <> payload)
  liftIO $ writeChan (outChan party) bytes

-- | increment program counter in state.
incPC :: SIO Int
incPC = do
    pcOld <- gets pc
    modify (\env -> env{pc = (+1) pcOld})
    gets pc

-- | 'forkIO' the action monad asynchronously and return future 'MVar'.
-- Provide the given state monad with its own program counter space.
async :: SIO a -> SIO (MVar a)
async = \action -> head <$> asyncList 1 ((:[]) <$> action)

asyncList :: Int -> SIO [a] -> SIO [MVar a]
asyncList l = \action -> head <$> asyncListList 1 l ((:[]) <$> action)

asyncListList :: Int -> Int -> SIO [[a]] -> SIO [[MVar a]]
asyncListList l1 l2 = \action -> do
    pcOld <- incPC
    state <- get
    outslist <- replicateM l1 $ replicateM l2 (liftIO $ newEmptyMVar)

    let newState = state{pc = hash $ show pcOld}
        barrier = forkIOBarrier state
        action2 = do
          fieldslist <- runSIO action newState
          zipWithM_ (zipWithM_ putMVar) outslist fieldslist
          decreaseBarrier barrier    

    liftIO $ modifyMVar_ (count barrier) (return . (+1))
    if (noAsync . options) state
      then liftIO $ action2
      else void $ liftIO $ forkIO $ action2

    return outslist 

decreaseBarrier :: Barrier -> IO ()
decreaseBarrier (Barrier countVar signalVar) =
          modifyMVar_ countVar $ \n -> do
            let n' = n - 1
            if n' == 0
              then do
                putMVar signalVar ()
                return n'
              else return n'