diff --git a/src/TahoeLAFS/Storage/Backend.hs b/src/TahoeLAFS/Storage/Backend.hs index 66e3677754d52154c4baeb461fbee4ca1c0396bc..dd99152eafbd481bab4162b8d8f3d891925e1f1f 100644 --- a/src/TahoeLAFS/Storage/Backend.hs +++ b/src/TahoeLAFS/Storage/Backend.hs @@ -46,7 +46,6 @@ data WriteImmutableError | ImmutableShareAlreadyWritten | ShareNotAllocated | IncorrectUploadSecret - | IncorrectWriteEnabler deriving (Ord, Eq, Show) instance Exception WriteImmutableError diff --git a/src/TahoeLAFS/Storage/Backend/Memory.hs b/src/TahoeLAFS/Storage/Backend/Memory.hs index cfe5e25d3c795480107d913a4a6f828948a38c3e..43228cc53f03515973dfc6edfe95099fab4f1131 100644 --- a/src/TahoeLAFS/Storage/Backend/Memory.hs +++ b/src/TahoeLAFS/Storage/Backend/Memory.hs @@ -17,7 +17,7 @@ import Data.IORef ( newIORef, readIORef, ) -import Data.Map.Merge.Strict (merge, preserveMissing, zipWithAMatched) +import Data.Map.Merge.Strict (merge, preserveMissing, zipWithMatched) import qualified Data.Map.Strict as Map import Data.Maybe (isNothing) import qualified Data.Set as Set @@ -57,12 +57,25 @@ data Bucket = Bucket data SecretProtected a = SecretProtected WriteEnablerSecret a -type ShareStorage = Map.Map StorageIndex (SecretProtected (Map.Map ShareNumber ShareData)) +readSecret :: SecretProtected a -> WriteEnablerSecret +readSecret (SecretProtected s _) = s -protectedWrite :: WriteEnablerSecret -> (a -> a) -> SecretProtected a -> Maybe (SecretProtected a) -protectedWrite proposed f (SecretProtected actual a) - | proposed == actual = Just (SecretProtected actual (f a)) - | otherwise = Nothing +readProtected :: SecretProtected a -> a +readProtected (SecretProtected _ p) = p + +{- | Apply a function in a SecretProtected to a value in a SecretProtected. The + result is in SecretProtected with the function's secret. + + This is almost liftA2 but it's not clear to me how to have lawful handling of + the secret. +-} +liftProtected2 :: (a -> a -> a) -> SecretProtected a -> SecretProtected a -> SecretProtected a +liftProtected2 f (SecretProtected secretL x) (SecretProtected _ y) = SecretProtected secretL (f x y) + +instance Functor SecretProtected where + fmap f (SecretProtected secret a) = SecretProtected secret (f a) + +type ShareStorage = Map.Map StorageIndex (SecretProtected (Map.Map ShareNumber ShareData)) data MemoryBackend = MemoryBackend { memoryBackendBuckets :: Map.Map StorageIndex Bucket -- Completely or partially written immutable share data @@ -179,27 +192,33 @@ instance Backend (IORef MemoryBackend) where getMutableShareNumbers :: IORef MemoryBackend -> StorageIndex -> IO (CBORSet ShareNumber) getMutableShareNumbers backend storageIndex = do - shares' <- mutableShares <$> readIORef backend - return $ - CBORSet . Set.fromList $ - maybe [] Map.keys $ - Map.lookup storageIndex shares' + sharemap <- fmap readProtected . Map.lookup storageIndex . mutableShares <$> readIORef backend + return + . CBORSet + . Set.fromList + . maybe [] Map.keys + $ sharemap readvAndTestvAndWritev :: IORef MemoryBackend -> StorageIndex -> WriteEnablerSecret -> ReadTestWriteVectors -> IO ReadTestWriteResult readvAndTestvAndWritev backend storageIndex - (WriteEnablerSecret secret) + secret (ReadTestWriteVectors testWritev _readv) = do -- TODO implement readv and testv parts. - -- TODO implement secret check -- TODO handle offsets correctly - modifyIORef backend $ \m@MemoryBackend{mutableShares} -> m{mutableShares = addShares storageIndex mutableShares (Map.map write testWritev)} + success <- atomicModifyIORef' backend tryWrite + return ReadTestWriteResult - { success = True - , readData = mempty + { readData = mempty + , success = success } + where + tryWrite m@MemoryBackend{mutableShares} = + case addShares storageIndex secret mutableShares (Map.map write testWritev) of + Nothing -> (m, False) + Just newShares -> (m{mutableShares = newShares}, True) createImmutableStorageIndex backend storageIndex secrets (AllocateBuckets shareNums size) = withUploadSecret secrets $ \secret -> @@ -232,21 +251,32 @@ totalShareSize backend = do let imm = memoryBackendBuckets backend mut = mutableShares backend let immSize = sum $ Map.map bucketTotalSize imm - let mutSize = sum $ Map.map length mut + let mutSize = sum $ Map.map (length . readProtected) mut return $ toInteger $ immSize + fromIntegral mutSize bucketTotalSize :: Bucket -> Size bucketTotalSize Bucket{bucketSize, bucketShares} = bucketSize * fromIntegral (Map.size bucketShares) -addShare :: StorageIndex -> ShareNumber -> [WriteVector] -> ShareStorage -> ShareStorage -addShare storageIndex shareNum writev = - Map.insertWith f storageIndex (Map.singleton shareNum (mconcat $ shareData <$> writev)) +addShare :: StorageIndex -> WriteEnablerSecret -> ShareNumber -> [WriteVector] -> ShareStorage -> ShareStorage +addShare storageIndex secret shareNum writev = + Map.insertWith (liftProtected2 f) storageIndex newShare where f :: Map.Map ShareNumber ShareData -> Map.Map ShareNumber ShareData -> Map.Map ShareNumber ShareData - f = merge preserveMissing preserveMissing (zipWithAMatched $ \_key _old new -> pure new) + f = merge preserveMissing preserveMissing (zipWithMatched applyWrites) + + applyWrites :: ShareNumber -> ShareData -> ShareData -> ShareData + applyWrites _ _ replacement = replacement -- XXX Need to merge new data into old, not replace it. Probably replace ShareData with a different type that makes this easier. + newShare = SecretProtected secret (Map.singleton shareNum (mconcat (shareData <$> writev))) + +addShares :: StorageIndex -> WriteEnablerSecret -> ShareStorage -> Map.Map ShareNumber [WriteVector] -> Maybe ShareStorage +addShares storageIndex secret existing updates + | isNothing writeEnabler = Just go + | writeEnabler == Just secret = Just go + | otherwise = Nothing + where + go = Map.foldrWithKey (addShare storageIndex secret) existing updates -addShares :: StorageIndex -> ShareStorage -> Map.Map ShareNumber [WriteVector] -> ShareStorage -addShares storageIndex = Map.foldrWithKey (addShare storageIndex) + writeEnabler = readSecret <$> Map.lookup storageIndex existing memoryBackend :: IO (IORef MemoryBackend) memoryBackend = do diff --git a/test/SemanticSpec.hs b/test/SemanticSpec.hs index a1150c73a97f60835677630eb5e9efe00b9aedd2..ae7c52548c6fc82da002ea697bfb29be90813fd3 100644 --- a/test/SemanticSpec.hs +++ b/test/SemanticSpec.hs @@ -22,6 +22,7 @@ import GHC.Word ( Word8, ) +import qualified Data.Map.Strict as Map import qualified Data.Set as Set import System.Directory ( @@ -38,11 +39,11 @@ import Test.Hspec ( context, describe, it, + shouldBe, shouldThrow, ) import Test.QuickCheck ( - Arbitrary (arbitrary), Positive (..), Property, forAll, @@ -55,27 +56,25 @@ import Test.QuickCheck.Monadic ( run, ) -import Data.ByteString ( - ByteString, - length, - map, - ) +import qualified Data.ByteString as B import TahoeLAFS.Storage.API ( AllocateBuckets (AllocateBuckets), AllocationResult (AllocationResult), CBORSet (..), LeaseSecret (..), - ReadTestWriteVectors (ReadTestWriteVectors), + ReadTestWriteResult (readData, success), + ReadTestWriteVectors, ShareData, ShareNumber (ShareNumber), Size, StorageIndex, - TestWriteVectors (TestWriteVectors), + TestWriteVectors, UploadSecret (UploadSecret), WriteEnablerSecret (WriteEnablerSecret), allocated, alreadyHave, + readv, toInteger, writev, ) @@ -106,16 +105,15 @@ import TahoeLAFS.Storage.Backend.Memory ( memoryBackend, ) -import Control.Monad.IO.Class (MonadIO (liftIO)) import Data.Data (Proxy (Proxy)) import TahoeLAFS.Storage.Backend.Filesystem ( FilesystemBackend (FilesystemBackend), ) import Test.QuickCheck.Classes (lawsCheck, semigroupMonoidLaws) -permuteShare :: ByteString -> ShareNumber -> ByteString +permuteShare :: B.ByteString -> ShareNumber -> B.ByteString permuteShare seed number = - Data.ByteString.map xor' seed + B.map xor' seed where xor' :: Word8 -> Word8 xor' = xor $ fromInteger $ toInteger number @@ -157,11 +155,11 @@ immutableWriteAndEnumerateShares :: IO b -> StorageIndex -> ShareNumbers -> - ByteString -> + B.ByteString -> Property immutableWriteAndEnumerateShares makeBackend storageIndex (ShareNumbers shareNumbers) shareSeed = monadicIO $ do let permutedShares = Prelude.map (permuteShare shareSeed) shareNumbers - size = fromIntegral (Data.ByteString.length shareSeed) + size = fromIntegral (B.length shareSeed) allocate = AllocateBuckets shareNumbers size run $ withBackend makeBackend $ \backend -> do @@ -180,10 +178,10 @@ immutableWriteAndRewriteShare :: IO b -> StorageIndex -> ShareNumbers -> - ByteString -> + B.ByteString -> Property immutableWriteAndRewriteShare makeBackend storageIndex (ShareNumbers shareNumbers) shareSeed = monadicIO $ do - let size = fromIntegral (Data.ByteString.length shareSeed) + let size = fromIntegral (B.length shareSeed) allocate = AllocateBuckets shareNumbers size aShareNumber = head shareNumbers aShare = permuteShare shareSeed aShareNumber @@ -204,11 +202,11 @@ immutableWriteAndReadShare :: IO b -> StorageIndex -> ShareNumbers -> - ByteString -> + B.ByteString -> Property immutableWriteAndReadShare makeBackend storageIndex (ShareNumbers shareNumbers) shareSeed = monadicIO $ do let permutedShares = Prelude.map (permuteShare shareSeed) shareNumbers - let size = fromIntegral (Data.ByteString.length shareSeed) + let size = fromIntegral (B.length shareSeed) let allocate = AllocateBuckets shareNumbers size run $ withBackend makeBackend $ \backend -> do @@ -227,7 +225,7 @@ mutableWriteAndEnumerateShares :: IO b -> StorageIndex -> ShareNumbers -> - ByteString -> + B.ByteString -> Property mutableWriteAndEnumerateShares makeBackend storageIndex (ShareNumbers shareNumbers) shareSeed = monadicIO $ do let permutedShares = Prelude.map (permuteShare shareSeed) shareNumbers @@ -312,14 +310,21 @@ storageSpec makeBackend = do forAll genStorageIndex (mutableWriteAndEnumerateShares makeBackend) it "rejects an update with the wrong write enabler" $ - property $ \storageIndex shareNum secret wrongSecret shareData offset -> - secret /= wrongSecret ==> monadicIO $ - run $ + forAll genStorageIndex $ \storageIndex shareNum secret wrongSecret shareData junkData offset -> + (secret /= wrongSecret) + && (shareData /= junkData) + && (B.length shareData > 0) + && (B.length junkData > 0) + ==> monadicIO + $ run $ withBackend makeBackend $ \backend -> do - void $ readvAndTestvAndWritev backend storageIndex (WriteEnablerSecret secret) (writev (ShareNumber shareNum) offset shareData) - readvAndTestvAndWritev backend storageIndex (WriteEnablerSecret wrongSecret) (writev (ShareNumber shareNum) offset shareData) - `shouldThrow` (== IncorrectWriteEnabler) + first <- readvAndTestvAndWritev backend storageIndex (WriteEnablerSecret secret) (writev shareNum offset shareData) + success first `shouldBe` True + second <- readvAndTestvAndWritev backend storageIndex (WriteEnablerSecret wrongSecret) (writev shareNum offset junkData) + success second `shouldBe` False + third <- readvAndTestvAndWritev backend storageIndex (WriteEnablerSecret secret) (readv offset (fromIntegral $ B.length shareData)) + readData third `shouldBe` Map.singleton shareNum [shareData] spec :: Spec spec = do