{-# LANGUAGE NoMonomorphismRestriction #-} {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE StrictData #-} {-# OPTIONS_GHC -Wno-missing-signatures #-} module Hjugement.CLI.Voter where import Control.Applicative (Applicative(..), Alternative(..)) import Control.Monad (Monad(..), forM, forM_, join, unless, void, when) import Control.Monad.Trans.Class (MonadTrans(..)) import Control.Monad.Trans.Maybe (MaybeT(..)) import Control.Monad.Trans.Except (runExcept, runExceptT) import Control.Monad.Trans.State.Strict (runState, runStateT) import Data.Bits (setBit) import Data.Bool import Data.ByteString (ByteString) import Data.Either (Either(..), either) import Data.Eq (Eq(..)) import Data.Foldable (Foldable, foldMap, length, null, sum) import Data.Function (($), (.), id, flip) import Data.Functor ((<$>), (<$)) import Data.Int (Int) import Data.Maybe (Maybe(..), maybe, fromMaybe, fromJust) import Data.Monoid (Monoid(..)) import Data.Ord (Ord(..)) import Data.Proxy (Proxy(..)) import Data.Semigroup (Semigroup(..)) import Data.String (String) import Data.Text (Text) import Data.Traversable (sequence) import GHC.Natural (minusNatural, minusNaturalMaybe) import GHC.Prim (coerce) import Numeric.Natural (Natural) import Pipes ((>->)) import Prelude (logBase, ceiling, Num(..), (/), (^), fromIntegral, Double) import Symantic.CLI as CLI import System.IO (IO, FilePath) import Text.Show (Show(..)) import qualified Crypto.Hash as Crypto import qualified Data.Aeson as JSON import qualified Data.ByteArray as ByteArray import qualified Data.ByteString as BS import qualified Data.ByteString.Char8 as BS8 import qualified Data.ByteString.Lazy as BSL import qualified Data.ByteString.Lazy.Char8 as BSL8 import qualified Data.List as List import qualified Data.Text as Text import qualified Data.Text.Encoding as Text import qualified Data.Text.Lazy as TL import qualified Data.Text.Lazy.Builder as TLB import qualified Data.Text.Lazy.Builder.Int as TLB import qualified Data.Text.Lazy.Encoding as TL import qualified Data.Text.Lazy.IO as TL import qualified Data.Time as Time import qualified Lens.Family as Lens import qualified Lens.Family.State.Strict as Lens import qualified Pipes as Pip import qualified Pipes.Aeson as PipJSON (DecodingError(..)) import qualified Pipes.Aeson.Unchecked as PipJSON import qualified Pipes.ByteString as PipBS import qualified Pipes.Group as Pip import qualified Pipes.Prelude as Pip import qualified Pipes.Safe as Pip import qualified Pipes.Safe.Prelude as Pip import qualified Pipes.Text as PipText import qualified Pipes.Text.Encoding as PipText import qualified Pipes.Text.IO as PipText import qualified Symantic.Document as Doc import qualified System.FilePath as FP import qualified System.IO as IO import qualified System.Posix as Posix import qualified System.Random as Rand import qualified Voting.Protocol as VP import qualified Voting.Protocol.Utils as VP import Hjugement.CLI.Utils -- * administrator data Voter_Params = Voter_Params { } deriving (Show) api_voter = "Commands for a voter." `helps` command "voter" $ api_voter_vote api_help False run_voter globParams = run_voter_vote globParams :!: run_help api_voter -- ** election data VoterVote_Params = VoterVote_Params { voterVote_privcred :: VP.Credential , voterVote_url :: FilePath , voterVote_grades :: [Text] } deriving (Show) api_voter_vote = "Cast a vote on an election." `helps` command "vote" $ rule "PARAMS" (VoterVote_Params <$> api_param_privcred <*> api_param_url <*> api_param_grades) response @(Maybe ()) where api_param_privcred = "Voter's private credential." `helps` requiredTag "privcred" (var "CREDENTIAL") api_param_grades = "The grades to evaluate the choices, from the lowest to the highest." `helps` many1Tag (TagLong "grade") $ var @Text "STRING" run_voter_vote Global_Params{..} o@VoterVote_Params{..} = runMaybeT $ do elecUnit <- loadJSON (voterVote_url FP. "election.json") VP.reifyElection elecUnit $ \(elec@VP.Election{..} :: VP.Election c) -> do outputInfo $ "Voted"<>Doc.from (show voterVote_grades) votes <- VP.isoZipWithM (outputError $ "Mismatching number of cast grades ("<> Doc.from (List.length voterVote_grades)<> ") and choices ("<> Doc.from (List.length election_questions)<> ")") (\VP.Question{..} grade -> do let bools = (grade ==) <$> question_choices let boolSum = sum $ (<$> bools) $ \b -> if b then 1 else 0 :: Natural unless (boolSum == 1) $ outputError $ "The election does not allow to cast a grade named: "<>Doc.from grade<>"\n"<> "Allowed grades are: "<>Doc.from (Text.intercalate ", " question_choices) return bools) election_questions voterVote_grades outputInfo $ Doc.from (show votes) let (secKey :: VP.SecretKey c) = VP.credentialSecretKey election_uuid voterVote_privcred ballot <- join $ Pip.liftIO $ Rand.getStdRandom $ \gen -> case runExcept $ (`runStateT` gen) $ VP.encryptBallot elec (Just secKey) votes of Left err -> (outputError $ Doc.from (show err), gen) Right (ballot, gen') -> (return ballot, gen') Pip.liftIO $ BSL8.putStrLn $ JSON.encode ballot