-- |
-- Module      : Marking
-- License     : BSD-3-Clause
-- Copyright   : (c) 2025 Olivier Chéron
--
-- Infrastructure that associates a security marking at type level to all
-- buffers created by the library.  This determines which buffers need the
-- scrubbed (Sec) or regular (Pub) variants.
--
{-# LANGUAGE CPP #-}
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE PolyKinds #-}
{-# LANGUAGE RankNTypes #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE TypeFamilyDependencies #-}
module Marking
    ( SecurityMarking(..), Classified(..), Leak(..), index
    , Marking.toNormalForm, unsafeCast
#ifdef ML_KEM_TESTING
    , Marking.toList
#endif
    ) where

import Control.DeepSeq (NFData(..))
import Control.Monad.ST

import Data.ByteArray (Bytes, ScrubbedBytes)
import qualified Data.ByteArray as B

import Data.Kind

import Foreign.Ptr (Ptr)

import Unsafe.Coerce

import Base
import Block (Block, MutableBlock, blockIndex)
import ScrubbedBlock (ScrubbedBlock)
import qualified Block
import qualified ByteArrayST as ST
import qualified ScrubbedBlock

data SecurityMarking = Sec | Pub  -- secret or public information

-- Transformation called only at expected location in the LWE problem, after
-- adding noise to secret information.
--
-- Block and ScrubbedBlock have the same representation, we can force coercion
-- from Sec to Pub even though the block will be actually scrubbed.  This is
-- simpler than copying to a real non-scrubbed block.
class Leak t where
    leak :: t Sec -> t Pub
    leak = unsafeCoerce

class Classified (marking :: SecurityMarking) where
    type SecureBlock marking = (block :: Type -> Type) | block -> marking

    new :: (PrimType ty, PrimMonad prim) => proxy marking -> CountOf ty -> prim (MutableBlock ty (PrimState prim))
    thaw :: PrimMonad m => SecureBlock marking ty -> m (MutableBlock ty (PrimState m))
    unsafeFreeze :: PrimMonad prim => MutableBlock ty (PrimState prim) -> prim (SecureBlock marking ty)

#ifdef ML_KEM_TESTING
    eq :: (Eq ty, PrimType ty) => SecureBlock marking ty -> SecureBlock marking ty -> Bool
    showsPrec :: (PrimType ty, Show ty) => Int -> SecureBlock marking ty -> ShowS
    lengthBlock :: PrimType ty => SecureBlock marking ty -> CountOf ty
#endif

    type SecureBytes marking = bytes | bytes -> marking
    unsafeCreate :: Int -> (forall s. Ptr a -> ST s ()) -> SecureBytes marking
    lengthBytes :: SecureBytes marking -> Int
    copyByteArrayToPtr :: SecureBytes marking -> Ptr a -> IO ()

instance Classified Pub where
    type SecureBlock Pub = Block

    new _ = Block.new
    thaw = Block.thaw
    unsafeFreeze = Block.unsafeFreeze

#ifdef ML_KEM_TESTING
    eq = (==)
    showsPrec = Prelude.showsPrec
    lengthBlock = Block.length
#endif

    type SecureBytes Pub = Bytes
    unsafeCreate = ST.unsafeCreate
    {-# INLINE unsafeCreate #-}
    lengthBytes = B.length
    copyByteArrayToPtr = B.copyByteArrayToPtr

instance Classified Sec where
    type SecureBlock Sec = ScrubbedBlock

    new _ = ScrubbedBlock.new
    thaw = ScrubbedBlock.thaw
    unsafeFreeze = ScrubbedBlock.unsafeFreeze

#ifdef ML_KEM_TESTING
    eq = (==)
    showsPrec = Prelude.showsPrec
    lengthBlock = ScrubbedBlock.length
#endif

    type SecureBytes Sec = ScrubbedBytes
    unsafeCreate = ST.unsafeCreate
    {-# INLINE unsafeCreate #-}
    lengthBytes = B.length
    copyByteArrayToPtr = B.copyByteArrayToPtr


-- for some functions we use the fact that Block and SecureBlock have the same
-- representation and implementation

unwrap :: SecureBlock marking a -> Block a
unwrap = unsafeCoerce

wrap :: Block b -> SecureBlock marking b
wrap = unsafeCoerce

index :: PrimType ty => SecureBlock marking ty -> Offset ty -> ty
index = blockIndex . unwrap

#ifdef ML_KEM_TESTING
toList :: PrimType ty => SecureBlock marking ty -> [ty]
toList = Block.toList . unwrap
#endif

toNormalForm :: SecureBlock marking ty -> ()
toNormalForm = rnf . unwrap

unsafeCast :: SecureBlock marking a -> SecureBlock marking b
unsafeCast = wrap . Block.unsafeCast . unwrap
