{-|
Module      : Crypto.RLWE.Challenges.Suppress
Description : Suppress a secret from a challenge.
Copyright   : (c) Eric Crockett, 2011-2017
                  Chris Peikert, 2011-2017
License     : GPL-2
Maintainer  : ecrockett0@email.com
Stability   : experimental
Portability : POSIX

Suppresses the instance secret for the official challenge instance.
-}

{-# LANGUAGE FlexibleContexts #-}

module Crypto.RLWE.Challenges.Suppress
(suppressMain, getNistCert, writeBeaconXML, suppressChallenge) where

import Crypto.RLWE.Challenges.Beacon
import Crypto.RLWE.Challenges.Common

import Control.Exception (try)
import Control.Monad.Except
import Control.Monad.State

import Crypto.Lol.Types.Proto
import Crypto.Proto.RLWE.Challenges.Challenge

import Data.ByteString.Lazy (writeFile)
import Data.ByteString.Char8 (unpack)
import Data.Maybe (isNothing)
import Data.Time.Clock.POSIX

import Data.Map             (Map, empty, insert, lookup)

import Net.Beacon
import Network.HTTP.Client hiding (path)
import Network.HTTP.Conduit (simpleHttp)

import Prelude hiding (lookup, writeFile)

import System.Directory (removeFile)
import System.Exit

-- | Deletes the secret indicated by NIST beacon for each challenge in
-- the tree, given the path to the root of the tree.
suppressMain :: FilePath -> IO ()
suppressMain path = do
  -- get list of challenges
  challs <- challengeList path

  -- suppress a secret from each challenge, collecting beacon records as we go
  recs <- flip execStateT empty $ mapM_ (suppressChallenge path) challs

  -- write all beacon records
  mapM_ (writeBeaconXML path) recs

  -- write the NIST certificate
  getNistCert path

-- | A map from beacon times to beacon records.
type RecordState = Map BeaconEpoch Record

-- | Lookup the secret index based on the randomness for this challenge,
-- then remove the corresponding secret.
suppressChallenge :: (MonadIO m, MonadState RecordState m)
                     => FilePath -> String -> m ()
suppressChallenge path name = do
  x <- printPassFail ("Deleting secret for challenge " ++ name ++ ":\n") "DONE" $ do
    -- read the beacon address of the randomness for this challenge
    let challFN = challFilePath path name
    challProto <- readProtoType challFN
    (BA time offset) <- parseBeaconAddr challProto
    let numInsts = fromIntegral $ numInstances challProto

    -- get the record, and compute the secret index
    rec <- retrieveRecord time
    let secID = suppressedSecretID numInsts rec offset
        secFile = secretFilePath path name secID

    -- delete the secret corresponding to the secret index
    checkFileExists secFile
    liftIO $ putStr $ "\tRemoving " ++ secFile ++ "\n\t"
    liftIO $ removeFile secFile
  when (isNothing x) $ liftIO $ die "To avoid publishing all instance secrets, we are dying early."
  return ()

-- | Attempt to find the record in the state, otherwise download it from NIST.
retrieveRecord :: (MonadIO m, MonadError String m, MonadState RecordState m)
                  => BeaconEpoch -> m Record
retrieveRecord t = do
  mrec <- gets (lookup t)
  case mrec of
    (Just r) -> return r
    Nothing -> do
      liftIO $ putStrLn $ "\tDownloading record " ++ show t
      trec <- liftIO $ try $ getCurrentRecord $ fromIntegral t
      rec <- case trec of
               Left e -> catchHttpException t e
               Right a -> return a
      rec' <- maybeThrowError rec $ "Couldn't parse XML for beacon at time " ++ show t
      modify (insert t rec')
      return rec'

catchHttpException :: (MonadIO m, MonadError String m)
  => BeaconEpoch -> HttpException -> m a
catchHttpException t (HttpExceptionRequest _ (StatusCodeException _ s)) = do
  currTime <- round <$> (liftIO getPOSIXTime)
  throwError $ case currTime < t of
    True -> "You are requesting a beacon that doesn't exist yet.\n" ++
            "Wait another " ++ show (t-currTime) ++ " seconds and try again."
    False -> "The beacon you are requesting should be available, " ++
             "but it just isn't there:\n" ++ unpack s
catchHttpException _ (HttpExceptionRequest _ (ConnectionFailure _)) =
  throwError "Failed to connect to NIST servers. They might be down for maintenance."
catchHttpException _ _ =
  throwError "An unexpected IO error occurred while downloading the beacon."

-- | Writes a beacon record to a file.
writeBeaconXML :: (MonadIO m) => FilePath -> Record -> m ()
writeBeaconXML path rec = do
  let beacon = toXML rec
      filePath = beaconFilePath path $ fromIntegral $ timeStamp rec
  liftIO $ writeFile filePath beacon

-- | Downloads the NIST certificate and saves it.
getNistCert :: (MonadIO m) => FilePath -> m ()
getNistCert path = liftIO $ do
  let certPath = certFilePath path
  putStrLn $ "Writing NIST certificate to " ++ certPath
  bs <- simpleHttp "https://beacon.nist.gov/certificate/beacon.cer"
  writeFile certPath bs