module Data.Serialize.RLP.Internal ( RLPEncodeable(..), -- * Helper Int functions toBigEndian, toBigEndianS, fromBigEndian, fromBigEndianS, -- * Helper String functions toByteString, toByteStringS, fromByteString, fromByteStringS, unJust, RLPT(..) ) where import Data.Binary.Get import Data.Binary.Put import qualified Data.ByteString as DBS import qualified Data.ByteString.Char8 as DBSC import qualified Data.ByteString.Lazy as DBSL import qualified Data.ByteString.Lazy.Char8 as DBSLC import qualified Data.List as DL -------------------------------------------------------------------------------- -- | The 'RLPT' type represents the result of transforming the -- initial data into its byte-array representation, taking in -- account the structure of the fields. -- -- Fields that can't be directly transformed into a ByteString (such -- as a type with several fields) should generate a list with the -- representations of its fields (using the RLPL constructor). -- -- RLPT represents the T type defined in the Ethereum Yellowpaper for -- defining the RLP protocol. data RLPT = RLPL [RLPT] | RLPB DBS.ByteString deriving (Show, Eq) -- just for understanding pourposes and for checking with hspec -------------------------------------------------------------------------------- toBigEndian :: Int -> DBSL.ByteString toBigEndian = DBSLC.dropWhile (== '\NUL') . runPut . putInt64be . fromIntegral -- | Strict version of 'toBigEndian' toBigEndianS :: Int -> DBS.ByteString toBigEndianS = DBSL.toStrict . toBigEndian fromBigEndian :: DBSL.ByteString -> Either String Int fromBigEndian bs = case bs'' of Left (_, _, msg) -> Left ("can't decode from Big-Endian: " ++ msg) Right (_, _, a) -> Right $ fromIntegral a where bs' = case () of _ | DBSL.length bs >= 8 -> bs | otherwise -> DBSLC.append (DBSLC.pack $ b) bs where b = take (8 - (fromIntegral . DBSL.length $ bs)) (repeat '\NUL') bs'' = runGetOrFail getInt64be $ bs' -- | Strict version of 'fromBigEndian' fromBigEndianS :: DBS.ByteString -> Either String Int fromBigEndianS = fromBigEndian . DBSL.fromStrict toByteString :: String -> DBSL.ByteString toByteString = DBSLC.pack -- | Strict version of 'toByteString' toByteStringS :: String -> DBS.ByteString toByteStringS = DBSC.pack fromByteString :: DBSL.ByteString -> String fromByteString = DBSLC.unpack -- | Strict version of 'fromByteString' fromByteStringS :: DBS.ByteString -> String fromByteStringS = DBSC.unpack -- | Internal function for spliting the array in chunks of bytes rlpSplit :: DBSL.ByteString -> Either String [DBSL.ByteString] rlpSplit x | DBSL.null x = Right [] | DBSL.head x < 192 = case () of _ | DBSL.head x < 128 -> let aux = rlpSplit $ DBSL.tail x in case aux of Left m -> Left m Right v -> Right $ (DBSL.singleton . DBSL.head $ x) : v | DBSL.head x < 183 -> let size = (fromIntegral $ DBSL.head x) - 128 :: Int in let total = size + 1 in let aux = rlpSplit $ DBSL.drop (fromIntegral total) x in case aux of Left m -> Left m Right v -> Right $ (DBSL.take (fromIntegral total) x) : v | otherwise -> let sizeSize = (fromIntegral $ DBSL.head x) - 183 :: Int in let size = fromBigEndian . DBSL.take (fromIntegral sizeSize) . DBSL.tail $ x in case size of Left m -> Left m Right v -> let total = sizeSize + v + 1 :: Int in let aux = rlpSplit $ DBSL.drop (fromIntegral total) x in case aux of Left m -> Left m Right v' -> Right $ (DBSL.take (fromIntegral total) x) : v' | DBSL.head x == 192 = let aux = (rlpSplit $ DBSL.tail x) in case aux of Left m -> Left m Right v -> Right $ (DBSL.singleton $ DBSL.head x) : v | DBSL.head x < 247 = let size = (fromIntegral $ DBSL.head x) - 192 :: Int in let total = size + 1 in let aux = rlpSplit $ DBSL.drop (fromIntegral total) x in case aux of Left m -> Left m Right v -> Right $ (DBSL.take (fromIntegral total) x) : v | otherwise = let sizeSize = (fromIntegral $ DBSL.head x) - 247 :: Int in let size = fromBigEndian . DBSL.take (fromIntegral sizeSize) . DBSL.tail $ x in case size of Left m -> Left m Right v -> let total = sizeSize + v + 1 :: Int in let aux = rlpSplit $ DBSL.drop (fromIntegral total) x in case aux of Left m -> Left m Right v' -> Right $ (DBSL.take (fromIntegral total) x) : v' -- Just for internal porpouses unJust :: Maybe a -> a unJust (Just x) = x unJust _ = undefined -------------------------------------------------------------------------------- -- | The 'RLPEncodeable' class groups the RLPT, ByteString and Int types -- for transforming them into ByteStrings. -- -- This class defines only the functions for the types explicitly shown on the -- Yellow Paper. This class intends to be internal and not be used outside the -- RLPSerialize class. class RLPEncodeable a where -- Use Put to encode the structure rlpEncodeI' :: a -> Put -- Mainly run rlpEncodeI' rlpEncodeI :: a -> DBSL.ByteString rlpEncodeI = runPut . rlpEncodeI' -- Use Get to parse the structure rlpDecodeI' :: Get a -- Mainly run rlpDecodeI' rlpDecodeI :: DBSL.ByteString -> Either String a rlpDecodeI x = let r = runGetOrFail rlpDecodeI' x in case r of Left (_, _, m) -> Left m Right (_, _, s) -> Right s -------------------------------------------------------------------------------- -- Instances instance RLPEncodeable RLPT where rlpEncodeI' (RLPB bs) = rlpEncodeI' bs rlpEncodeI' (RLPL t) = case () of _ | DBSL.length dat < 56 -> (putWord8 . fromIntegral $ 192 + DBSL.length dat) <> (putLazyByteString dat) | otherwise -> (putWord8 . fromIntegral $ 247 + DBSL.length l) <> (putLazyByteString l) <> (putLazyByteString dat) where l = toBigEndian . fromIntegral . DBSL.length $ dat where dat = DBSL.concat . map rlpEncodeI $ t rlpDecodeI' = do i <- getWord8 case () of _ | i < 192 -> do -- ByteArray ls <- getRemainingLazyByteString let r = rlpDecodeI $ DBSL.cons i ls case r of Left m -> fail m Right v -> return . RLPB $ v | i == 192 -> do -- Empty list return $ RLPL [] | i < 247 -> do -- Small list ls <- getLazyByteString . fromIntegral $ i - 192 let k = rlpSplit ls case k of Left m -> fail m Right v -> do let k' = map rlpDecodeI v let k'' = map (\e -> case e of Left m -> m Right _ -> "") k' case all null k'' of True -> return $ RLPL . map (\(Right x) -> x) $ k' _ -> fail (DL.intercalate ", " k'') | otherwise -> do -- Big List ls <- getLazyByteString . fromIntegral $ i - 247 let k = fromBigEndian ls case k of Left m -> fail m Right v -> do ls' <- getLazyByteString . fromIntegral $ v let k' = rlpSplit ls' case k' of Left m' -> fail m' Right v' -> do let k'' = map rlpDecodeI v' let k'3 = map (\e -> case e of Left m -> m Right _ -> "") k'' case all null k'3 of True -> return $ RLPL . map (\(Right x) -> x) $ k'' _ -> fail (DL.intercalate ", " k'3) instance RLPEncodeable DBS.ByteString where rlpEncodeI' bs | (DBS.length bs == 1) && (DBS.head bs < (fromIntegral (128 :: Integer))) = putByteString bs | DBS.length bs < 56 = (putWord8 . fromIntegral $ 128 + DBS.length bs) <> (putByteString bs) | otherwise = (putWord8 . fromIntegral $ 183 + DBSL.length l) <> (putLazyByteString l) <> (putByteString bs) where l = toBigEndian . DBS.length $ bs rlpDecodeI' = do i <- getWord8 case () of _ | i < 128 -> return $ DBS.singleton i | i < 183 -> do ls <- getByteString . fromIntegral $ i - 128 return ls | i < 192 -> do sbe <- getLazyByteString . fromIntegral $ i - 183 let k = fromBigEndian sbe case k of Left m -> fail m Right v -> do ls <- getByteString v return ls | otherwise -> fail "Decoding a ByteString with head >= 192" instance RLPEncodeable Int where rlpEncodeI' = rlpEncodeI' . toBigEndianS rlpDecodeI' = do b <- rlpDecodeI' :: Get DBS.ByteString case DBSC.head b of '\NUL' -> fail "leading zeroes found when decoding an integer" _ -> do let k = fromBigEndianS b case k of Left m -> fail m Right v -> return v