/
PatchApplier.hs
100 lines (86 loc) · 3.81 KB
/
PatchApplier.hs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
{-# LANGUAGE LambdaCase, DeriveDataTypeable, ScopedTypeVariables #-}
module SSync.PatchApplier (patchApplier, PatchException(..)) where
import Conduit
import Control.Applicative ((<$>))
import Control.Exception (Exception)
import Control.Monad (when)
import Control.Monad.Except (ExceptT(..), withExceptT, throwError, runExceptT)
import Data.ByteString (ByteString)
import qualified Data.ByteString as BS
import qualified Data.ByteString.Lazy as BSL
import Data.Serialize.Get (Get, getWord8, getBytes)
import Data.Text (Text)
import Data.Typeable (Typeable)
import Data.Word (Word8, Word32)
import SSync.Constants
import SSync.Hash
import SSync.PatchComputer
import SSync.Util
import SSync.Util.Cereal hiding (consumeAndHash, getVarInt)
import qualified SSync.Util.Cereal as SC
type ChunkProvider m = Int -> Word32 -> m (Maybe ByteString) -- blocksize/number
data PatchException = UnexpectedEOF
| ExpectedEOF
| MalformedInteger
| BlockSizeTooLarge Word32
| DataBlockTooLarge Word32
| UnknownBlockType Word8
| UnknownBlock Word32
| UnknownChecksum Text
| ChecksumMismatch
deriving (Show, Typeable)
instance Exception PatchException
consumeAndHash :: (Monad m) => ExceptT PatchException Get a -> ExceptT PatchException (HashT (ConduitM ByteString o m)) a
consumeAndHash = SC.consumeAndHash UnexpectedEOF
getVarInt :: ExceptT PatchException Get Word32
getVarInt = withExceptT fixup SC.getVarInt
where fixup MalformedVarInt = MalformedInteger
fixupEOF :: String -> PatchException
fixupEOF _ = UnexpectedEOF
withHashTEx :: (Monad m) => HashAlgorithm -> ExceptT e (HashT m) a -> ExceptT e m a
withHashTEx ha = ExceptT . withHashT ha . runExceptT
patchApplier :: (MonadThrow m) => ChunkProvider m -> Conduit ByteString m ByteString
patchApplier chunkProvider = orThrow $ do
checksumAlgName <- withExceptT fixupEOF $ ExceptT (sinkGet' getShortString)
checksumAlg <- maybe (throwError $ UnknownChecksum checksumAlgName) return (forName checksumAlgName)
expectedDigest <- withHashTEx checksumAlg $ do
blockSize <- consumeAndHash getBlockSize
when (blockSize > maxBlockSize) $ throwError (BlockSizeTooLarge blockSize)
let blockSizeI = fromIntegral blockSize -- we know it'll fit in an Int now
process blockSize (chunkProvider blockSizeI)
digestS
actualDigest <- withExceptT fixupEOF $ ExceptT (sinkGet' $ getBytes $ BS.length expectedDigest)
when (expectedDigest /= actualDigest) $ do
throwError ChecksumMismatch
lift awaitNonEmpty >>= \case
Nothing -> return ()
Just _ -> throwError ExpectedEOF
process :: (Monad m) => Word32 -> (Word32 -> m (Maybe ByteString)) -> ExceptT PatchException (HashT (ConduitM ByteString ByteString m)) ()
process blockSize chunkProvider =
consumeAndHash (getChunk blockSize) >>= \case
Just (Data bytes) ->
mapM_ (lift . lift . yield) (BSL.toChunks bytes) >> process blockSize chunkProvider
Just (Block num) ->
(lift . lift . lift) (chunkProvider num) >>= \case
Just bytes -> (lift . lift . yield) bytes >> process blockSize chunkProvider
Nothing -> throwError $ UnknownBlock num
Nothing ->
return ()
getBlockSize :: ExceptT PatchException Get Word32
getBlockSize = do
blockSize <- getVarInt
when (blockSize > maxBlockSize) $ throwError (BlockSizeTooLarge blockSize)
return blockSize
getChunk :: Word32 -> ExceptT PatchException Get (Maybe Chunk)
getChunk blockSize =
lift getWord8 >>= \case
0 ->
Just . Block <$> getVarInt
1 -> do
len <- getVarInt
when (len > blockSize) $ throwError (DataBlockTooLarge len)
Just . Data <$> lift (getLazyBytes (fromIntegral len))
255 ->
return Nothing
other ->
throwError $ UnknownBlockType other