hnix-store-remote-0.7.0.0: src/System/Nix/Store/Remote/Serializer.hs
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
{-|
Description : NixSerializer
Copyright : (c) John Ericson, 2023
Sorki, 2023
|-}
module System.Nix.Store.Remote.Serializer
(
-- * NixSerializer
NixSerializer
, mapReaderS
, mapErrorS
-- * Errors
, SError(..)
-- ** Runners
, runSerialT
, runG
, runP
-- * Primitives
, int
, bool
, byteString
, enum
, text
, maybeText
-- * UTCTime
, time
-- * Combinators
, list
, set
, hashSet
, mapS
, vector
, json
-- * ProtoVersion
, protoVersion
-- * StorePath
, storePath
, maybePath
, storePathHashPart
, storePathName
-- * Metadata
, pathMetadata
-- * OutputName
, outputName
-- * Signatures
, signature
, narSignature
-- * Some HashAlgo
, someHashAlgo
-- * Digest
, digest
-- * DSum HashAlgo Digest
, namedDigest
-- * Derivation
, derivation
-- * Derivation
, derivedPath
-- * Build
, buildMode
-- * Logger
, LoggerSError(..)
, activityID
, maybeActivity
, activity
, activityResult
, field
, trace
, basicError
, errorInfo
, loggerOpCode
, logger
, verbosity
-- * Handshake
, HandshakeSError(..)
, workerMagic
, trustedFlag
-- * Worker protocol
, storeText
, workerOp
-- ** Request
, RequestSError(..)
, storeRequest
-- ** Reply
, ReplySError(..)
, opSuccess
, noop
-- *** Realisation
, derivationOutputTyped
, realisation
, realisationWithId
-- *** BuildResult
, buildResult
-- *** GCResult
, gcResult
-- *** GCResult
, gcRoot
-- *** Missing
, missing
-- *** Maybe (Metadata StorePath)
, maybePathMetadata
) where
import Control.Monad.Except (MonadError, throwError, )
import Control.Monad.Reader (MonadReader)
import Control.Monad.Trans (MonadTrans, lift)
import Control.Monad.Trans.Reader (ReaderT, runReaderT, withReaderT)
import Control.Monad.Trans.Except (ExceptT, mapExceptT, runExceptT, withExceptT)
import Crypto.Hash (Digest, HashAlgorithm, SHA256)
import Data.Aeson (FromJSON, ToJSON)
import Data.ByteString (ByteString)
import Data.Dependent.Sum (DSum((:=>)))
import Data.Fixed (Uni)
import Data.Hashable (Hashable)
import Data.HashSet (HashSet)
import Data.Map (Map)
import Data.Serializer
import Data.Set (Set)
import Data.Some (Some(Some))
import Data.Text (Text)
import Data.Text.Lazy.Builder (Builder)
import Data.Time (NominalDiffTime, UTCTime)
import Data.Vector (Vector)
import Data.Word (Word8, Word32, Word64)
import GHC.Generics (Generic)
import System.Nix.Base (BaseEncoding(Base16, NixBase32))
import System.Nix.Build (BuildMode, BuildResult(..))
import System.Nix.ContentAddress (ContentAddress)
import System.Nix.Derivation (Derivation(..), DerivationOutput(..))
import System.Nix.DerivedPath (DerivedPath(..), ParseOutputsError)
import System.Nix.Hash (HashAlgo(..))
import System.Nix.JSON ()
import System.Nix.OutputName (OutputName)
import System.Nix.Realisation (DerivationOutputError, Realisation(..), RealisationWithId(..))
import System.Nix.Signature (Signature, NarSignature)
import System.Nix.Store.Types (FileIngestionMethod(..), RepairMode(..))
import System.Nix.StorePath (HasStoreDir(..), InvalidNameError, InvalidPathError, StorePath, StorePathHashPart, StorePathName)
import System.Nix.StorePath.Metadata (Metadata(..), StorePathTrust(..))
import System.Nix.Store.Remote.Types
import qualified Control.Monad
import qualified Control.Monad.Reader
import qualified Data.Aeson
import qualified Data.Attoparsec.Text
import qualified Data.Bifunctor
import qualified Data.Bits
import qualified Data.ByteString
import qualified Data.ByteString.Char8
import qualified Data.ByteString.Lazy
import qualified Data.Coerce
import qualified Data.HashSet
import qualified Data.Map.Strict
import qualified Data.Maybe
import qualified Data.Serialize.Get
import qualified Data.Serialize.Put
import qualified Data.Set
import qualified Data.Some
import qualified Data.Text
import qualified Data.Text.Encoding
import qualified Data.Text.Lazy
import qualified Data.Text.Lazy.Builder
import qualified Data.Time.Clock.POSIX
import qualified Data.Vector
import qualified System.Nix.Base
import qualified System.Nix.ContentAddress
import qualified System.Nix.DerivedPath
import qualified System.Nix.Hash
import qualified System.Nix.OutputName
import qualified System.Nix.Realisation
import qualified System.Nix.Signature
import qualified System.Nix.StorePath
-- | Transformer for @Serializer@
newtype SerialT r e m a = SerialT
{ _unSerialT :: ExceptT e (ReaderT r m) a }
deriving
( Applicative
, Functor
, Monad
, MonadError e
, MonadReader r
, MonadFail
)
instance MonadTrans (SerialT r e) where
lift = SerialT . lift . lift
-- | Runner for @SerialT@
runSerialT
:: Monad m
=> r
-> SerialT r e m a
-> m (Either e a)
runSerialT r =
(`runReaderT` r)
. runExceptT
. _unSerialT
mapErrorST
:: Functor m
=> (e -> e')
-> SerialT r e m a
-> SerialT r e' m a
mapErrorST f =
SerialT
. withExceptT f
. _unSerialT
mapErrorS
:: (e -> e')
-> NixSerializer r e a
-> NixSerializer r e' a
mapErrorS f s = Serializer
{ getS = mapErrorST f $ getS s
, putS = mapErrorST f . putS s
}
mapReaderST
:: Functor m
=> (r' -> r)
-> SerialT r e m a
-> SerialT r' e m a
mapReaderST f =
SerialT
. (mapExceptT . withReaderT) f
. _unSerialT
mapReaderS
:: (r' -> r)
-> NixSerializer r e a
-> NixSerializer r' e a
mapReaderS f s = Serializer
{ getS = mapReaderST f $ getS s
, putS = mapReaderST f . putS s
}
-- * NixSerializer
type NixSerializer r e = Serializer (SerialT r e)
-- * Errors
data SError
= SError
| SError_BadPadding
{ badPaddingStr :: ByteString
, badPaddingLen :: Int
, badPaddingPads :: [Word8]
}
| SError_ContentAddress String
| SError_DerivedPath ParseOutputsError
| SError_DerivationOutput DerivationOutputError
| SError_Digest String
| SError_EnumOutOfMinBound Int
| SError_EnumOutOfMaxBound Int
| SError_HashAlgo String
| SError_IllegalBool Word64
| SError_InvalidNixBase32
| SError_JSONDecoding String
| SError_NarHashMustBeSHA256
| SError_NotYetImplemented String (ForPV ProtoVersion)
| SError_Name InvalidNameError
| SError_Path InvalidPathError
| SError_Signature String
deriving (Eq, Ord, Generic, Show)
data ForPV a
= ForPV_Newer a
| ForPV_Older a
deriving (Eq, Ord, Generic, Show)
-- ** Runners
runG
:: NixSerializer r e a
-> r
-> ByteString
-> Either (GetSerializerError e) a
runG serializer r =
transformGetError
. runGetS
serializer
(runSerialT r)
runP
:: NixSerializer r e a
-> r
-> a
-> Either e ByteString
runP serializer r =
transformPutError
. runPutS
serializer
(runSerialT r)
-- * Primitives
int :: Integral a => NixSerializer r e a
int = Serializer
{ getS = fromIntegral <$> lift Data.Serialize.Get.getWord64le
, putS = lift . Data.Serialize.Put.putWord64le . fromIntegral
}
bool :: NixSerializer r SError Bool
bool = Serializer
{ getS = getS (int @Word64) >>= \case
0 -> pure False
1 -> pure True
x -> throwError $ SError_IllegalBool x
, putS = \case
False -> putS (int @Word8) 0
True -> putS (int @Word8) 1
}
byteString :: NixSerializer r SError ByteString
byteString = Serializer
{ getS = do
len <- getS int
st <- lift $ Data.Serialize.Get.getByteString len
Control.Monad.when (len `mod` 8 /= 0) $ do
pads <- unpad $ fromIntegral $ 8 - (len `mod` 8)
Control.Monad.unless
(all (== 0) pads)
$ throwError
$ SError_BadPadding st len pads
pure st
, putS = \x -> do
let len = Data.ByteString.length x
putS int len
lift $ Data.Serialize.Put.putByteString x
Control.Monad.when
(len `mod` 8 /= 0)
$ pad $ 8 - (len `mod` 8)
}
where
unpad count =
Control.Monad.replicateM
count
(lift Data.Serialize.Get.getWord8)
pad count =
Control.Monad.replicateM_
count
(lift $ Data.Serialize.Put.putWord8 0)
-- | Utility toEnum version checking bounds using Bounded class
toEnumCheckBoundsM
:: forall a m
. ( Bounded a
, Enum a
, MonadError SError m
)
=> Int
-> m a
toEnumCheckBoundsM = \case
x | x < fromEnum (minBound @a) -> throwError $ SError_EnumOutOfMinBound x
x | x > fromEnum (maxBound @a) -> throwError $ SError_EnumOutOfMaxBound x
x | otherwise -> pure $ toEnum x
enum
:: ( Bounded a
, Enum a
)
=> NixSerializer r SError a
enum = Serializer
{ getS = getS int >>= toEnumCheckBoundsM
, putS = putS int . fromEnum
}
text :: NixSerializer r SError Text
text = mapIsoSerializer
Data.Text.Encoding.decodeUtf8
Data.Text.Encoding.encodeUtf8
byteString
-- TODO Parser Builder
_textBuilder :: NixSerializer r SError Builder
_textBuilder = Serializer
{ getS = Data.Text.Lazy.Builder.fromText <$> getS text
, putS = putS text . Data.Text.Lazy.toStrict . Data.Text.Lazy.Builder.toLazyText
}
maybeText :: NixSerializer r SError (Maybe Text)
maybeText = mapIsoSerializer
(\case
t | Data.Text.null t -> Nothing
t | otherwise -> Just t
)
(Data.Maybe.fromMaybe mempty)
text
-- * UTCTime
time :: NixSerializer r e UTCTime
time = Serializer
{ getS =
Data.Time.Clock.POSIX.posixSecondsToUTCTime
. toPicoSeconds
<$> getS int
, putS =
putS int
. fromPicoSeconds
. Data.Time.Clock.POSIX.utcTimeToPOSIXSeconds
}
where
-- fancy (*10^12), from Int to Uni to Pico(seconds)
toPicoSeconds :: Int -> NominalDiffTime
toPicoSeconds n = realToFrac (toEnum n :: Uni)
-- fancy (`div`10^12), from Pico to Uni to Int
fromPicoSeconds :: NominalDiffTime -> Int
fromPicoSeconds = (fromEnum :: Uni -> Int) . realToFrac
-- * Combinators
list
:: NixSerializer r e a
-> NixSerializer r e [a]
list s = Serializer
{ getS = do
count <- getS int
Control.Monad.replicateM count (getS s)
, putS = \xs -> do
putS int (length xs)
mapM_ (putS s) xs
}
set
:: Ord a
=> NixSerializer r e a
-> NixSerializer r e (Set a)
set =
mapIsoSerializer
Data.Set.fromList
Data.Set.toList
. list
hashSet
:: ( Eq a
, Hashable a
)
=> NixSerializer r e a
-> NixSerializer r e (HashSet a)
hashSet =
mapIsoSerializer
Data.HashSet.fromList
Data.HashSet.toList
. list
mapS
:: Ord k
=> NixSerializer r e k
-> NixSerializer r e v
-> NixSerializer r e (Map k v)
mapS k v =
mapIsoSerializer
Data.Map.Strict.fromList
Data.Map.Strict.toList
$ list
$ tup k v
vector
:: Ord a
=> NixSerializer r e a
-> NixSerializer r e (Vector a)
vector =
mapIsoSerializer
Data.Vector.fromList
Data.Vector.toList
. list
json
:: ( FromJSON a
, ToJSON a
)
=> NixSerializer r SError a
json =
mapPrismSerializer
( Data.Bifunctor.first SError_JSONDecoding
. Data.Aeson.eitherDecode
)
Data.Aeson.encode
$ mapIsoSerializer
Data.ByteString.Lazy.fromStrict
Data.ByteString.Lazy.toStrict
byteString
-- * ProtoVersion
-- protoVersion_major & 0xFF00
-- protoVersion_minor & 0x00FF
protoVersion :: NixSerializer r e ProtoVersion
protoVersion = Serializer
{ getS = do
v <- getS (int @Word32)
pure ProtoVersion
{ protoVersion_major = fromIntegral $ Data.Bits.shiftR v 8
, protoVersion_minor = fromIntegral $ v Data.Bits..&. 0x00FF
}
, putS = \p ->
putS (int @Word32)
$ ((Data.Bits.shiftL (fromIntegral $ protoVersion_major p :: Word32) 8)
Data.Bits..|. fromIntegral (protoVersion_minor p))
}
-- * StorePath
storePath :: HasStoreDir r => NixSerializer r SError StorePath
storePath = Serializer
{ getS = do
sd <- Control.Monad.Reader.asks hasStoreDir
System.Nix.StorePath.parsePath sd <$> getS byteString
>>=
either
(throwError . SError_Path)
pure
, putS = \p -> do
sd <- Control.Monad.Reader.asks hasStoreDir
putS
byteString
$ System.Nix.StorePath.storePathToRawFilePath sd p
}
maybePath
:: HasStoreDir r
=> NixSerializer r SError (Maybe StorePath)
maybePath = Serializer
{ getS = do
getS maybeText >>= \case
Nothing -> pure Nothing
Just t -> do
sd <- Control.Monad.Reader.asks hasStoreDir
either
(throwError . SError_Path)
(pure . pure)
$ System.Nix.StorePath.parsePathFromText sd t
, putS = \case
Nothing -> putS maybeText Nothing
Just p -> do
sd <- Control.Monad.Reader.asks hasStoreDir
putS text $ System.Nix.StorePath.storePathToText sd p
}
storePathHashPart :: NixSerializer r SError StorePathHashPart
storePathHashPart =
mapIsoSerializer
System.Nix.StorePath.unsafeMakeStorePathHashPart
System.Nix.StorePath.unStorePathHashPart
$ mapPrismSerializer
(Data.Bifunctor.first (pure SError_InvalidNixBase32)
. System.Nix.Base.decodeWith NixBase32)
(System.Nix.Base.encodeWith NixBase32)
text
storePathName :: NixSerializer r SError StorePathName
storePathName =
mapPrismSerializer
(Data.Bifunctor.first SError_Name
. System.Nix.StorePath.mkStorePathName)
System.Nix.StorePath.unStorePathName
text
pathMetadata
:: HasStoreDir r
=> NixSerializer r SError (Metadata StorePath)
pathMetadata = Serializer
{ getS = do
metadataDeriverPath <- getS maybePath
digest' <- getS $ digest Base16
let metadataNarHash = System.Nix.Hash.HashAlgo_SHA256 :=> digest'
metadataReferences <- getS $ hashSet storePath
metadataRegistrationTime <- getS time
metadataNarBytes <-
(\case
0 -> Nothing
size -> Just size
) <$> getS int
metadataTrust <- getS storePathTrust
metadataSigs <- getS $ set narSignature
metadataContentAddress <- getS maybeContentAddress
pure $ Metadata{..}
, putS = \Metadata{..} -> do
putS maybePath metadataDeriverPath
let putNarHash
:: DSum HashAlgo Digest
-> SerialT r SError PutM ()
putNarHash = \case
System.Nix.Hash.HashAlgo_SHA256 :=> d
-> putS (digest @SHA256 Base16) d
_ -> throwError SError_NarHashMustBeSHA256
putNarHash metadataNarHash
putS (hashSet storePath) metadataReferences
putS time metadataRegistrationTime
putS int $ Data.Maybe.fromMaybe 0 metadataNarBytes
putS storePathTrust metadataTrust
putS (set narSignature) metadataSigs
putS maybeContentAddress metadataContentAddress
}
where
maybeContentAddress
:: NixSerializer r SError (Maybe ContentAddress)
maybeContentAddress =
mapPrismSerializer
(maybe
(pure Nothing)
$ Data.Bifunctor.bimap
SError_ContentAddress
Just
. System.Nix.ContentAddress.parseContentAddress
)
(fmap System.Nix.ContentAddress.buildContentAddress)
maybeText
storePathTrust
:: NixSerializer r SError StorePathTrust
storePathTrust =
mapIsoSerializer
(\case False -> BuiltElsewhere; True -> BuiltLocally)
(\case BuiltElsewhere -> False; BuiltLocally -> True)
bool
-- * OutputName
outputName :: NixSerializer r SError OutputName
outputName =
mapPrismSerializer
(Data.Bifunctor.first SError_Name
. System.Nix.OutputName.mkOutputName)
System.Nix.OutputName.unOutputName
text
-- * Signatures
signature
:: NixSerializer r SError Signature
signature =
mapPrismSerializer
(Data.Bifunctor.first SError_Signature
. Data.Attoparsec.Text.parseOnly
System.Nix.Signature.signatureParser)
(System.Nix.Signature.signatureToText)
text
narSignature
:: NixSerializer r SError NarSignature
narSignature =
mapPrismSerializer
(Data.Bifunctor.first SError_Signature
. Data.Attoparsec.Text.parseOnly
System.Nix.Signature.narSignatureParser)
(System.Nix.Signature.narSignatureToText)
text
-- * Some HashAlgo
someHashAlgo :: NixSerializer r SError (Some HashAlgo)
someHashAlgo =
mapPrismSerializer
(Data.Bifunctor.first SError_HashAlgo
. System.Nix.Hash.textToAlgo)
(Data.Some.foldSome System.Nix.Hash.algoToText)
text
-- * Digest
digest
:: forall a r
. HashAlgorithm a
=> BaseEncoding
-> NixSerializer r SError (Digest a)
digest base =
mapIsoSerializer
Data.Coerce.coerce
Data.Coerce.coerce
$ mapPrismSerializer
(Data.Bifunctor.first SError_Digest
. System.Nix.Hash.decodeDigestWith @a base)
(System.Nix.Hash.encodeDigestWith base)
$ text
-- * DSum HashAlgo Digest
namedDigest :: NixSerializer r SError (DSum HashAlgo Digest)
namedDigest = Serializer
{ getS = do
sriHash <- getS text
let (sriName, _h) = Data.Text.breakOn (Data.Text.singleton '-') sriHash
-- bit hacky since mkNamedDigest does the check
-- that the expected matches but we don't know
-- what we expect here (i.e. handle each HashAlgo)
case System.Nix.Hash.mkNamedDigest sriName sriHash of
Left e -> throwError $ SError_Digest e
Right x -> pure x
-- TODO: we also lack a builder for SRI hashes
-- , putS = putS textBuilder . System.Nix.Hash.algoDigestBuilder
, putS = \(algo :=> d) -> do
putS text
$ System.Nix.Hash.algoToText algo
<> (Data.Text.singleton '-')
<> System.Nix.Hash.encodeDigestWith NixBase32 d
}
derivationOutput
:: HasStoreDir r
=> NixSerializer r SError (DerivationOutput StorePath Text)
derivationOutput = Serializer
{ getS = do
path <- getS storePath
hashAlgo <- getS text
hash <- getS text
pure DerivationOutput{..}
, putS = \DerivationOutput{..} -> do
putS storePath path
putS text hashAlgo
putS text hash
}
-- * Derivation
derivation
:: HasStoreDir r
=> NixSerializer r SError (Derivation StorePath Text)
derivation = Serializer
{ getS = do
outputs <- getS (mapS text derivationOutput)
-- Our type is Derivation, but in Nix
-- the type sent over the wire is BasicDerivation
-- which omits inputDrvs
inputDrvs <- pure mempty
inputSrcs <- getS (set storePath)
platform <- getS text
builder <- getS text
args <- getS (vector text)
env <- getS (mapS text text)
pure Derivation{..}
, putS = \Derivation{..} -> do
putS (mapS text derivationOutput) outputs
putS (set storePath) inputSrcs
putS text platform
putS text builder
putS (vector text) args
putS (mapS text text) env
}
-- * DerivedPath
derivedPathNew
:: HasStoreDir r
=> NixSerializer r SError DerivedPath
derivedPathNew = Serializer
{ getS = do
root <- Control.Monad.Reader.asks hasStoreDir
p <- getS text
case System.Nix.DerivedPath.parseDerivedPath root p of
Left err -> throwError $ SError_DerivedPath err
Right x -> pure x
, putS = \d -> do
root <- Control.Monad.Reader.asks hasStoreDir
putS text (System.Nix.DerivedPath.derivedPathToText root d)
}
derivedPath
:: ( HasProtoVersion r
, HasStoreDir r
)
=> NixSerializer r SError DerivedPath
derivedPath = Serializer
{ getS = do
pv <- Control.Monad.Reader.asks hasProtoVersion
if pv < ProtoVersion 1 30
then DerivedPath_Opaque <$> getS storePath
else getS derivedPathNew
, putS = \d -> do
pv <- Control.Monad.Reader.asks hasProtoVersion
if pv < ProtoVersion 1 30
then case d of
DerivedPath_Opaque p -> putS storePath p
_ -> throwError
$ SError_NotYetImplemented
"DerivedPath_Built"
(ForPV_Older pv)
else putS derivedPathNew d
}
-- * Build
buildMode :: NixSerializer r SError BuildMode
buildMode = enum
-- * Logger
data LoggerSError
= LoggerSError_Prim SError
| LoggerSError_InvalidOpCode Word64
| LoggerSError_TooOldForErrorInfo
| LoggerSError_TooNewForBasicError
| LoggerSError_UnknownLogFieldType Word8
deriving (Eq, Ord, Generic, Show)
mapPrimE
:: Functor m
=> SerialT r SError m a
-> SerialT r LoggerSError m a
mapPrimE = mapErrorST LoggerSError_Prim
maybeActivity :: NixSerializer r LoggerSError (Maybe Activity)
maybeActivity = Serializer
{ getS = getS (int @Int) >>= \case
0 -> pure Nothing
x -> mapPrimE $ toEnumCheckBoundsM (x - 100) >>= pure . Just
, putS = \case
Nothing -> putS (int @Int) 0
Just act -> putS activity act
}
activity :: NixSerializer r LoggerSError Activity
activity = Serializer
{ getS = mapPrimE $ getS int >>= toEnumCheckBoundsM . (+(-100))
, putS = putS int . (+100) . fromEnum
}
activityID :: NixSerializer r LoggerSError ActivityID
activityID = mapIsoSerializer ActivityID unActivityID int
activityResult :: NixSerializer r LoggerSError ActivityResult
activityResult = Serializer
{ getS = mapPrimE $ getS int >>= toEnumCheckBoundsM . (+(-100))
, putS = putS int . (+100) . fromEnum
}
field :: NixSerializer r LoggerSError Field
field = Serializer
{ getS = getS (int @Word8) >>= \case
0 -> Field_LogInt <$> getS int
1 -> Field_LogStr <$> mapPrimE (getS text)
x -> throwError $ LoggerSError_UnknownLogFieldType x
, putS = \case
Field_LogInt x -> putS int (0 :: Word8) >> putS int x
Field_LogStr x -> putS int (1 :: Word8) >> mapPrimE (putS text x)
}
trace :: NixSerializer r LoggerSError Trace
trace = Serializer
{ getS = do
tracePosition <- (\case 0 -> Nothing; x -> Just x) <$> getS (int @Int)
traceHint <- mapPrimE $ getS text
pure Trace{..}
, putS = \Trace{..} -> do
putS int $ Data.Maybe.fromMaybe 0 tracePosition
mapPrimE $ putS text traceHint
}
basicError :: NixSerializer r LoggerSError BasicError
basicError = Serializer
{ getS = do
basicErrorMessage <- mapPrimE $ getS text
basicErrorExitStatus <- getS int
pure BasicError{..}
, putS = \BasicError{..} -> do
mapPrimE $ putS text basicErrorMessage
putS int basicErrorExitStatus
}
errorInfo :: NixSerializer r LoggerSError ErrorInfo
errorInfo = Serializer
{ getS = do
etyp <- mapPrimE $ getS text
Control.Monad.unless (etyp == Data.Text.pack "Error")
$ fail
$ "get ErrorInfo: received unknown error type" ++ show etyp
errorInfoLevel <- getS verbosity
_name <- mapPrimE $ getS text -- removed error name
errorInfoMessage <- mapPrimE $ getS text
errorInfoPosition <- (\case 0 -> Nothing; x -> Just x) <$> getS int
errorInfoTraces <- getS (list trace)
pure ErrorInfo{..}
, putS = \ErrorInfo{..} -> do
mapPrimE $ do
putS text $ Data.Text.pack "Error"
putS verbosity errorInfoLevel
mapPrimE $ do
putS text $ Data.Text.pack "Error" -- removed error name
putS text errorInfoMessage
putS int $ Data.Maybe.fromMaybe 0 errorInfoPosition
putS (list trace) errorInfoTraces
}
loggerOpCode :: NixSerializer r LoggerSError LoggerOpCode
loggerOpCode = Serializer
{ getS = do
c <- getS int
either
(pure $ throwError (LoggerSError_InvalidOpCode c))
pure
$ word64ToLoggerOpCode c
, putS = putS int . loggerOpCodeToWord64
}
logger
:: HasProtoVersion r
=> NixSerializer r LoggerSError Logger
logger = Serializer
{ getS = getS loggerOpCode >>= \case
LoggerOpCode_Next ->
mapPrimE $
Logger_Next <$> getS text
LoggerOpCode_Read ->
Logger_Read <$> getS int
LoggerOpCode_Write ->
mapPrimE $
Logger_Write <$> getS byteString
LoggerOpCode_Last ->
pure Logger_Last
LoggerOpCode_Error -> do
pv <- Control.Monad.Reader.asks hasProtoVersion
Logger_Error <$>
if protoVersion_minor pv >= 26
then Right <$> getS errorInfo
else Left <$> getS basicError
LoggerOpCode_StartActivity -> do
startActivityID <- getS activityID
startActivityVerbosity <- getS verbosity
startActivityType <- getS maybeActivity
startActivityString <- mapPrimE $ getS byteString
startActivityFields <- getS (list field)
startActivityParentID <- getS activityID
pure Logger_StartActivity{..}
LoggerOpCode_StopActivity -> do
stopActivityID <- getS activityID
pure Logger_StopActivity{..}
LoggerOpCode_Result -> do
resultActivityID <- getS activityID
resultType <- getS activityResult
resultFields <- getS (list field)
pure Logger_Result {..}
, putS = \case
Logger_Next s -> do
putS loggerOpCode LoggerOpCode_Next
mapPrimE $ putS text s
Logger_Read i -> do
putS loggerOpCode LoggerOpCode_Read
putS int i
Logger_Write s -> do
putS loggerOpCode LoggerOpCode_Write
mapPrimE $ putS byteString s
Logger_Last ->
putS loggerOpCode LoggerOpCode_Last
Logger_Error basicOrInfo -> do
putS loggerOpCode LoggerOpCode_Error
minor <- protoVersion_minor <$> Control.Monad.Reader.asks hasProtoVersion
case basicOrInfo of
Left _ | minor >= 26 -> throwError $ LoggerSError_TooNewForBasicError
Left e | otherwise -> putS basicError e
Right _ | minor < 26 -> throwError $ LoggerSError_TooOldForErrorInfo
Right e -> putS errorInfo e
Logger_StartActivity{..} -> do
putS loggerOpCode LoggerOpCode_StartActivity
putS activityID startActivityID
putS verbosity startActivityVerbosity
putS maybeActivity startActivityType
mapPrimE $
putS byteString startActivityString
putS (list field) startActivityFields
putS activityID startActivityParentID
Logger_StopActivity{..} -> do
putS loggerOpCode LoggerOpCode_StopActivity
putS activityID stopActivityID
Logger_Result{..} -> do
putS loggerOpCode LoggerOpCode_Result
putS activityID resultActivityID
putS activityResult resultType
putS (list field) resultFields
}
verbosity :: NixSerializer r LoggerSError Verbosity
verbosity = Serializer
{ getS = mapPrimE $ getS enum
, putS = mapPrimE . putS enum
}
-- * Handshake
data HandshakeSError
= HandshakeSError_InvalidWorkerMagic Word64
| HandshakeSError_InvalidTrustedFlag Word8
deriving (Eq, Ord, Generic, Show)
workerMagic :: NixSerializer r HandshakeSError WorkerMagic
workerMagic = Serializer
{ getS = do
c <- getS int
either
(pure $ throwError (HandshakeSError_InvalidWorkerMagic c))
pure
$ word64ToWorkerMagic c
, putS = putS int . workerMagicToWord64
}
trustedFlag :: NixSerializer r HandshakeSError (Maybe TrustedFlag)
trustedFlag = Serializer
{ getS = do
n :: Word8 <- getS int
case n of
0 -> return $ Nothing
1 -> return $ Just TrustedFlag_Trusted
2 -> return $ Just TrustedFlag_NotTrusted
_ -> throwError (HandshakeSError_InvalidTrustedFlag n)
, putS = \n -> putS int $ case n of
Nothing -> 0 :: Word8
Just TrustedFlag_Trusted -> 1
Just TrustedFlag_NotTrusted -> 2
}
-- * Worker protocol
storeText :: NixSerializer r SError StoreText
storeText = Serializer
{ getS = do
storeTextName <- getS storePathName
storeTextText <- getS text
pure StoreText{..}
, putS = \StoreText{..} -> do
putS storePathName storeTextName
putS text storeTextText
}
workerOp :: NixSerializer r SError WorkerOp
workerOp = enum
-- * Request
data RequestSError
= RequestSError_NotYetImplemented WorkerOp
| RequestSError_ReservedOp WorkerOp
| RequestSError_PrimGet SError
| RequestSError_PrimPut SError
| RequestSError_PrimWorkerOp SError
deriving (Eq, Ord, Generic, Show)
storeRequest
:: ( HasProtoVersion r
, HasStoreDir r
)
=> NixSerializer r RequestSError (Some StoreRequest)
storeRequest = Serializer
{ getS = mapErrorST RequestSError_PrimWorkerOp (getS workerOp) >>= \case
WorkerOp_AddToStore -> mapGetE $ do
pathName <- getS storePathName
_fixed <- getS bool -- obsolete
recursive <- getS enum
hashAlgo <- getS someHashAlgo
-- not supported by ProtoVersion < 1.25
let repair = RepairMode_DontRepair
pure $ Some (AddToStore pathName recursive hashAlgo repair)
WorkerOp_AddToStoreNar -> mapGetE $ do
storePath' <- getS storePath
metadata <- getS pathMetadata
repair <- getS bool
let repairMode = if repair then RepairMode_DoRepair else RepairMode_DontRepair
dontCheckSigs <- getS bool
let checkSigs = if dontCheckSigs then CheckMode_DontCheck else CheckMode_DoCheck
pure $ Some (AddToStoreNar storePath' metadata repairMode checkSigs)
WorkerOp_AddTextToStore -> mapGetE $ do
txt <- getS storeText
paths <- getS (hashSet storePath)
let repair = RepairMode_DontRepair
pure $ Some (AddTextToStore txt paths repair)
WorkerOp_AddSignatures -> mapGetE $ do
path <- getS storePath
signatures <- getS (set signature)
pure $ Some (AddSignatures path signatures)
WorkerOp_AddIndirectRoot -> mapGetE $ do
Some . AddIndirectRoot <$> getS storePath
WorkerOp_AddTempRoot -> mapGetE $ do
Some . AddTempRoot <$> getS storePath
WorkerOp_BuildPaths -> mapGetE $ do
derived <- getS (set derivedPath)
buildMode' <- getS buildMode
pure $ Some (BuildPaths derived buildMode')
WorkerOp_BuildDerivation -> mapGetE $ do
path <- getS storePath
drv <- getS derivation
buildMode' <- getS buildMode
pure $ Some (BuildDerivation path drv buildMode')
WorkerOp_CollectGarbage -> mapGetE $ do
gcOptionsOperation <- getS enum
gcOptionsPathsToDelete <- getS (hashSet storePath)
gcOptionsIgnoreLiveness <- getS bool
gcOptionsMaxFreed <- getS int
-- obsolete fields
Control.Monad.forM_ [0..(2 :: Word8)]
$ pure $ getS (int @Word8)
pure $ Some (CollectGarbage GCOptions{..})
WorkerOp_EnsurePath -> mapGetE $ do
Some . EnsurePath <$> getS storePath
WorkerOp_FindRoots -> mapGetE $ do
pure $ Some FindRoots
WorkerOp_IsValidPath -> mapGetE $ do
Some . IsValidPath <$> getS storePath
WorkerOp_NarFromPath -> mapGetE $ do
Some . NarFromPath <$> getS storePath
WorkerOp_QueryValidPaths -> mapGetE $ do
paths <- getS (hashSet storePath)
substituteMode <- getS enum
pure $ Some (QueryValidPaths paths substituteMode)
WorkerOp_QueryAllValidPaths -> mapGetE $ do
pure $ Some QueryAllValidPaths
WorkerOp_QuerySubstitutablePaths -> mapGetE $ do
Some . QuerySubstitutablePaths <$> getS (hashSet storePath)
WorkerOp_QueryPathInfo -> mapGetE $ do
Some . QueryPathInfo <$> getS storePath
WorkerOp_QueryReferrers -> mapGetE $ do
Some . QueryReferrers <$> getS storePath
WorkerOp_QueryValidDerivers -> mapGetE $ do
Some . QueryValidDerivers <$> getS storePath
WorkerOp_QueryDerivationOutputs -> mapGetE $ do
Some . QueryDerivationOutputs <$> getS storePath
WorkerOp_QueryDerivationOutputNames -> mapGetE $ do
Some . QueryDerivationOutputNames <$> getS storePath
WorkerOp_QueryPathFromHashPart -> mapGetE $ do
Some . QueryPathFromHashPart <$> getS storePathHashPart
WorkerOp_QueryMissing -> mapGetE $ do
Some . QueryMissing <$> getS (set derivedPath)
WorkerOp_OptimiseStore -> mapGetE $ do
pure $ Some OptimiseStore
WorkerOp_SyncWithGC -> mapGetE $ do
pure $ Some SyncWithGC
WorkerOp_VerifyStore -> mapGetE $ do
checkMode <- getS enum
repairMode <- getS enum
pure $ Some (VerifyStore checkMode repairMode)
w@WorkerOp_Reserved_0__ -> reserved w
w@WorkerOp_Reserved_2__ -> reserved w
w@WorkerOp_Reserved_15__ -> reserved w
w@WorkerOp_Reserved_17__ -> reserved w
w@WorkerOp_AddBuildLog -> notYet w
w@WorkerOp_AddMultipleToStore -> notYet w
w@WorkerOp_BuildPathsWithResults -> notYet w
w@WorkerOp_ClearFailedPaths -> notYet w
w@WorkerOp_ExportPath -> notYet w
w@WorkerOp_HasSubstitutes -> notYet w
w@WorkerOp_ImportPaths -> notYet w
w@WorkerOp_QueryDerivationOutputMap -> notYet w
w@WorkerOp_QueryDeriver -> notYet w
w@WorkerOp_QueryFailedPaths -> notYet w
w@WorkerOp_QueryPathHash -> notYet w
w@WorkerOp_QueryRealisation -> notYet w
w@WorkerOp_QuerySubstitutablePathInfo -> notYet w
w@WorkerOp_QuerySubstitutablePathInfos -> notYet w
w@WorkerOp_QueryReferences -> notYet w
w@WorkerOp_RegisterDrvOutput -> notYet w
w@WorkerOp_SetOptions -> notYet w
, putS = \case
Some (AddToStore pathName recursive hashAlgo _repair) -> mapPutE $ do
putS workerOp WorkerOp_AddToStore
putS storePathName pathName
-- obsolete fixed
putS bool
$ not
$ hashAlgo == Some HashAlgo_SHA256
&& (recursive == FileIngestionMethod_FileRecursive)
putS bool (recursive == FileIngestionMethod_FileRecursive)
putS someHashAlgo hashAlgo
Some (AddToStoreNar storePath' metadata repair checkSigs) -> mapPutE $ do
putS workerOp WorkerOp_AddToStoreNar
putS storePath storePath'
putS pathMetadata metadata
putS bool $ repair == RepairMode_DoRepair
putS bool $ checkSigs == CheckMode_DontCheck
Some (AddTextToStore txt paths _repair) -> mapPutE $ do
putS workerOp WorkerOp_AddTextToStore
putS storeText txt
putS (hashSet storePath) paths
Some (AddSignatures path signatures) -> mapPutE $ do
putS workerOp WorkerOp_AddSignatures
putS storePath path
putS (set signature) signatures
Some (AddIndirectRoot path) -> mapPutE $ do
putS workerOp WorkerOp_AddIndirectRoot
putS storePath path
Some (AddTempRoot path) -> mapPutE $ do
putS workerOp WorkerOp_AddTempRoot
putS storePath path
Some (BuildPaths derived buildMode') -> mapPutE $ do
putS workerOp WorkerOp_BuildPaths
putS (set derivedPath) derived
putS buildMode buildMode'
Some (BuildDerivation path drv buildMode') -> mapPutE $ do
putS workerOp WorkerOp_BuildDerivation
putS storePath path
putS derivation drv
putS buildMode buildMode'
Some (CollectGarbage GCOptions{..}) -> mapPutE $ do
putS workerOp WorkerOp_CollectGarbage
putS enum gcOptionsOperation
putS (hashSet storePath) gcOptionsPathsToDelete
putS bool gcOptionsIgnoreLiveness
putS int gcOptionsMaxFreed
-- obsolete fields
Control.Monad.forM_ [0..(2 :: Word8)]
$ pure $ putS int (0 :: Word8)
Some (EnsurePath path) -> mapPutE $ do
putS workerOp WorkerOp_EnsurePath
putS storePath path
Some FindRoots -> mapPutE $ do
putS workerOp WorkerOp_FindRoots
Some (IsValidPath path) -> mapPutE $ do
putS workerOp WorkerOp_IsValidPath
putS storePath path
Some (NarFromPath path) -> mapPutE $ do
putS workerOp WorkerOp_NarFromPath
putS storePath path
Some (QueryValidPaths paths substituteMode) -> mapPutE $ do
putS workerOp WorkerOp_QueryValidPaths
putS (hashSet storePath) paths
putS enum substituteMode
Some QueryAllValidPaths -> mapPutE $ do
putS workerOp WorkerOp_QueryAllValidPaths
Some (QuerySubstitutablePaths paths) -> mapPutE $ do
putS workerOp WorkerOp_QuerySubstitutablePaths
putS (hashSet storePath) paths
Some (QueryPathInfo path) -> mapPutE $ do
putS workerOp WorkerOp_QueryPathInfo
putS storePath path
Some (QueryReferrers path) -> mapPutE $ do
putS workerOp WorkerOp_QueryReferrers
putS storePath path
Some (QueryValidDerivers path) -> mapPutE $ do
putS workerOp WorkerOp_QueryValidDerivers
putS storePath path
Some (QueryDerivationOutputs path) -> mapPutE $ do
putS workerOp WorkerOp_QueryDerivationOutputs
putS storePath path
Some (QueryDerivationOutputNames path) -> mapPutE $ do
putS workerOp WorkerOp_QueryDerivationOutputNames
putS storePath path
Some (QueryPathFromHashPart pathHashPart) -> mapPutE $ do
putS workerOp WorkerOp_QueryPathFromHashPart
putS storePathHashPart pathHashPart
Some (QueryMissing derived) -> mapPutE $ do
putS workerOp WorkerOp_QueryMissing
putS (set derivedPath) derived
Some OptimiseStore -> mapPutE $ do
putS workerOp WorkerOp_OptimiseStore
Some SyncWithGC -> mapPutE $ do
putS workerOp WorkerOp_SyncWithGC
Some (VerifyStore checkMode repairMode) -> mapPutE $ do
putS workerOp WorkerOp_VerifyStore
putS enum checkMode
putS enum repairMode
}
where
mapGetE
:: Functor m
=> SerialT r SError m a
-> SerialT r RequestSError m a
mapGetE = mapErrorST RequestSError_PrimGet
mapPutE
:: Functor m
=> SerialT r SError m a
-> SerialT r RequestSError m a
mapPutE = mapErrorST RequestSError_PrimPut
notYet
:: MonadError RequestSError m
=> WorkerOp
-> m a
notYet = throwError . RequestSError_NotYetImplemented
reserved
:: MonadError RequestSError m
=> WorkerOp
-> m a
reserved = throwError . RequestSError_ReservedOp
-- ** Reply
data ReplySError
= ReplySError_PrimGet SError
| ReplySError_PrimPut SError
| ReplySError_DerivationOutput SError
| ReplySError_GCResult SError
| ReplySError_Metadata SError
| ReplySError_Missing SError
| ReplySError_Realisation SError
| ReplySError_RealisationWithId SError
| ReplySError_UnexpectedFalseOpSuccess
deriving (Eq, Ord, Generic, Show)
mapGetER
:: Functor m
=> SerialT r SError m a
-> SerialT r ReplySError m a
mapGetER = mapErrorST ReplySError_PrimGet
mapPutER
:: Functor m
=> SerialT r SError m a
-> SerialT r ReplySError m a
mapPutER = mapErrorST ReplySError_PrimPut
-- | Parse a bool returned at the end of simple operations.
-- This is always 1 (@True@) so we assert that it really is so.
-- Errors for these operations are indicated via @Logger_Error@.
opSuccess :: NixSerializer r ReplySError SuccessCodeReply
opSuccess = Serializer
{ getS = do
retCode <- mapGetER $ getS bool
Control.Monad.unless
(retCode == True)
$ throwError ReplySError_UnexpectedFalseOpSuccess
pure SuccessCodeReply
, putS = \_ -> mapPutER $ putS bool True
}
noop :: a -> NixSerializer r ReplySError a
noop ret = Serializer
{ getS = pure ret
, putS = \_ -> pure ()
}
-- *** Realisation
derivationOutputTyped :: NixSerializer r ReplySError (System.Nix.Realisation.DerivationOutput OutputName)
derivationOutputTyped = mapErrorS ReplySError_DerivationOutput $
mapPrismSerializer
( Data.Bifunctor.first SError_DerivationOutput
. System.Nix.Realisation.derivationOutputParser
System.Nix.OutputName.mkOutputName
)
( Data.Text.Lazy.toStrict
. Data.Text.Lazy.Builder.toLazyText
. System.Nix.Realisation.derivationOutputBuilder
System.Nix.OutputName.unOutputName
)
text
realisation :: NixSerializer r ReplySError Realisation
realisation = mapErrorS ReplySError_Realisation json
realisationWithId :: NixSerializer r ReplySError RealisationWithId
realisationWithId = mapErrorS ReplySError_RealisationWithId json
-- *** BuildResult
buildResult
:: ( HasProtoVersion r
, HasStoreDir r
)
=> NixSerializer r ReplySError BuildResult
buildResult = Serializer
{ getS = do
pv <- Control.Monad.Reader.asks hasProtoVersion
buildResultStatus <- mapGetER $ getS enum
buildResultErrorMessage <- mapGetER $ getS maybeText
( buildResultTimesBuilt
, buildResultIsNonDeterministic
, buildResultStartTime
, buildResultStopTime
) <-
if protoVersion_minor pv >= 29
then mapGetER $ do
tb <- (\case 0 -> Nothing; x -> Just x) <$> getS int
nondet <- getS bool
start <- (\case x | x == t0 -> Nothing; x -> Just x) <$> getS time
end <- (\case x | x == t0 -> Nothing; x -> Just x) <$> getS time
pure $ (tb, pure nondet, start, end)
else pure $ (Nothing, Nothing, Nothing, Nothing)
buildResultBuiltOutputs <-
if protoVersion_minor pv >= 28
then
pure
. Data.Map.Strict.fromList
. map (\(_, RealisationWithId (a, b)) -> (a, b))
. Data.Map.Strict.toList
<$> getS (mapS derivationOutputTyped realisationWithId)
else pure Nothing
pure BuildResult{..}
, putS = \BuildResult{..} -> do
pv <- Control.Monad.Reader.asks hasProtoVersion
mapPutER $ putS enum buildResultStatus
mapPutER $ putS maybeText buildResultErrorMessage
Control.Monad.when (protoVersion_minor pv >= 29) $ mapPutER $ do
putS int $ Data.Maybe.fromMaybe 0 buildResultTimesBuilt
putS bool $ Data.Maybe.fromMaybe False buildResultIsNonDeterministic
putS time $ Data.Maybe.fromMaybe t0 buildResultStartTime
putS time $ Data.Maybe.fromMaybe t0 buildResultStopTime
Control.Monad.when (protoVersion_minor pv >= 28)
$ putS (mapS derivationOutputTyped realisationWithId)
$ Data.Map.Strict.fromList
$ map (\(a, b) -> (a, RealisationWithId (a, b)))
$ Data.Map.Strict.toList
$ Data.Maybe.fromMaybe mempty buildResultBuiltOutputs
}
where
t0 :: UTCTime
t0 = Data.Time.Clock.POSIX.posixSecondsToUTCTime 0
-- *** GCResult
gcResult
:: HasStoreDir r
=> NixSerializer r ReplySError GCResult
gcResult = mapErrorS ReplySError_GCResult $ Serializer
{ getS = do
gcResultDeletedPaths <- getS (hashSet storePath)
gcResultBytesFreed <- getS int
Control.Monad.void $ getS (int @Word64) -- obsolete
pure GCResult{..}
, putS = \GCResult{..} -> do
putS (hashSet storePath) gcResultDeletedPaths
putS int gcResultBytesFreed
putS (int @Word64) 0 -- obsolete
}
-- *** GCRoot
gcRoot :: NixSerializer r ReplySError GCRoot
gcRoot = Serializer
{ getS = mapGetER $ do
getS byteString >>= \case
p | p == censored -> pure GCRoot_Censored
p -> pure (GCRoot_Path p)
, putS = mapPutER . putS byteString . \case
GCRoot_Censored -> censored
GCRoot_Path p -> p
}
where censored = Data.ByteString.Char8.pack "{censored}"
-- *** Missing
missing
:: HasStoreDir r
=> NixSerializer r ReplySError Missing
missing = mapErrorS ReplySError_Missing $ Serializer
{ getS = do
missingWillBuild <- getS (hashSet storePath)
missingWillSubstitute <- getS (hashSet storePath)
missingUnknownPaths <- getS (hashSet storePath)
missingDownloadSize <- getS int
missingNarSize <- getS int
pure Missing{..}
, putS = \Missing{..} -> do
putS (hashSet storePath) missingWillBuild
putS (hashSet storePath) missingWillSubstitute
putS (hashSet storePath) missingUnknownPaths
putS int missingDownloadSize
putS int missingNarSize
}
-- *** Maybe (Metadata StorePath)
maybePathMetadata
:: HasStoreDir r
=> NixSerializer r ReplySError (Maybe (Metadata StorePath))
maybePathMetadata = mapErrorS ReplySError_Metadata $ Serializer
{ getS = do
valid <- getS bool
if valid
then pure <$> getS pathMetadata
else pure Nothing
, putS = \case
Nothing -> putS bool False
Just pm -> putS bool True >> putS pathMetadata pm
}