1 {-# LANGUAGE NoMonomorphismRestriction #-}
2 {-# LANGUAGE OverloadedStrings #-}
3 {-# LANGUAGE StrictData #-}
4 {-# OPTIONS_GHC -Wno-missing-signatures #-}
5 module Hjugement.CLI.Voter where
7 import Control.Applicative (Applicative(..), Alternative(..))
8 import Control.Monad (Monad(..), forM, forM_, join, unless, void, when)
9 import Control.Monad.Trans.Class (MonadTrans(..))
10 import Control.Monad.Trans.Maybe (MaybeT(..))
11 import Control.Monad.Trans.Except (runExcept, runExceptT)
12 import Control.Monad.Trans.State.Strict (runState, runStateT)
13 import Data.Bits (setBit)
15 import Data.ByteString (ByteString)
16 import Data.Either (Either(..), either)
17 import Data.Eq (Eq(..))
18 import Data.Foldable (Foldable, foldMap, length, null, sum)
19 import Data.Function (($), (.), id, flip)
20 import Data.Functor ((<$>), (<$))
22 import Data.Maybe (Maybe(..), maybe, fromMaybe, fromJust)
23 import Data.Monoid (Monoid(..))
24 import Data.Ord (Ord(..))
25 import Data.Proxy (Proxy(..))
26 import Data.Semigroup (Semigroup(..))
27 import Data.String (String)
28 import Data.Text (Text)
29 import Data.Traversable (sequence)
30 import GHC.Natural (minusNatural, minusNaturalMaybe)
31 import GHC.Prim (coerce)
32 import Numeric.Natural (Natural)
34 import Prelude (logBase, ceiling, Num(..), (/), (^), fromIntegral, Double)
35 import Symantic.CLI as CLI
36 import System.IO (IO, FilePath)
37 import Text.Show (Show(..))
38 import qualified Crypto.Hash as Crypto
39 import qualified Data.Aeson as JSON
40 import qualified Data.ByteArray as ByteArray
41 import qualified Data.ByteString as BS
42 import qualified Data.ByteString.Char8 as BS8
43 import qualified Data.ByteString.Lazy as BSL
44 import qualified Data.ByteString.Lazy.Char8 as BSL8
45 import qualified Data.List as List
46 import qualified Data.Text as Text
47 import qualified Data.Text.Encoding as Text
48 import qualified Data.Text.Lazy as TL
49 import qualified Data.Text.Lazy.Builder as TLB
50 import qualified Data.Text.Lazy.Builder.Int as TLB
51 import qualified Data.Text.Lazy.Encoding as TL
52 import qualified Data.Text.Lazy.IO as TL
53 import qualified Data.Time as Time
54 import qualified Lens.Family as Lens
55 import qualified Lens.Family.State.Strict as Lens
56 import qualified Pipes as Pip
57 import qualified Pipes.Aeson as PipJSON (DecodingError(..))
58 import qualified Pipes.Aeson.Unchecked as PipJSON
59 import qualified Pipes.ByteString as PipBS
60 import qualified Pipes.Group as Pip
61 import qualified Pipes.Prelude as Pip
62 import qualified Pipes.Safe as Pip
63 import qualified Pipes.Safe.Prelude as Pip
64 import qualified Pipes.Text as PipText
65 import qualified Pipes.Text.Encoding as PipText
66 import qualified Pipes.Text.IO as PipText
67 import qualified Symantic.Document as Doc
68 import qualified System.FilePath as FP
69 import qualified System.IO as IO
70 import qualified System.Posix as Posix
71 import qualified System.Random as Rand
72 import qualified Voting.Protocol as VP
73 import qualified Voting.Protocol.Utils as VP
75 import Hjugement.CLI.Utils
78 data Voter_Params = Voter_Params
83 "Commands for a voter."
88 run_voter globParams =
89 run_voter_vote globParams
90 :!: run_help api_voter
93 data VoterVote_Params = VoterVote_Params
94 { voterVote_privcred :: VP.Credential
95 , voterVote_url :: FilePath
96 , voterVote_grades :: [Text]
100 "Cast a vote on an election."
105 <$> api_param_privcred
107 <*> api_param_grades)
108 <?> response @(Maybe ())
111 "Voter's private credential."
113 requiredTag "privcred" (var "CREDENTIAL")
115 "The grades to evaluate the choices, from the lowest to the highest."
117 many1Tag (TagLong "grade") $
122 o@VoterVote_Params{..}
124 elecUnit <- loadJSON (voterVote_url FP.</> "election.json")
125 VP.reifyElection elecUnit $ \(elec@VP.Election{..} :: VP.Election c) -> do
126 outputInfo $ "Voted"<>Doc.from (show voterVote_grades)
127 votes <- VP.isoZipWithM
128 (outputError $ "Mismatching number of cast grades ("<>
129 Doc.from (List.length voterVote_grades)<>
131 Doc.from (List.length election_questions)<>
133 (\VP.Question{..} grade -> do
134 let bools = (grade ==) <$> question_choices
135 let boolSum = sum $ (<$> bools) $ \b -> if b then 1 else 0 :: Natural
136 unless (boolSum == 1) $
138 "The election does not allow to cast a grade named: "<>Doc.from grade<>"\n"<>
139 "Allowed grades are: "<>Doc.from (Text.intercalate ", " question_choices)
143 outputInfo $ Doc.from (show votes)
144 let (secKey :: VP.SecretKey c) =
145 VP.credentialSecretKey election_uuid voterVote_privcred
146 ballot <- join $ Pip.liftIO $
147 Rand.getStdRandom $ \gen ->
148 case runExcept $ (`runStateT` gen) $
149 VP.encryptBallot elec (Just secKey) votes of
150 Left err -> (outputError $ Doc.from (show err), gen)
151 Right (ballot, gen') -> (return ballot, gen')
152 Pip.liftIO $ BSL8.putStrLn $ JSON.encode ballot