{-# LANGUAGE BinaryLiterals #-} {-# LANGUAGE LambdaCase #-} {-# LANGUAGE NegativeLiterals #-} {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE ScopedTypeVariables #-} {-# LANGUAGE TemplateHaskell #-} import Hedgehog import qualified Hedgehog.Gen as Gen import qualified Hedgehog.Range as Range import Control.Exception.Safe import Data.Bits import Data.Bool import Data.ByteString (ByteString) import qualified Data.ByteString as ByteString import Data.Foldable import Data.Int import qualified Data.Map as Map import Data.Maybe import Data.Monoid import Data.Packer import Data.Packer.MessagePack import Data.Proxy import Data.Text (Text) import qualified Data.Text.Encoding as Text import Data.Word data Bit = Bit0 | Bit1 deriving (Eq, Show, Ord) data BitException = BitParseFailure String deriving (Show, Typeable) instance Exception BitException showBits :: [Bit] -> String showBits = map convert where convert Bit0 = '0' convert Bit1 = '1' parseBits :: MonadThrow m => String -> m [Bit] parseBits s = case parseBits' s of Right bits -> return bits Left _ -> throwM $ BitParseFailure "Failure to bits" parseBits' :: String -> Either String [Bit] parseBits' = sequence . map parseBit where parseBit '1' = Right Bit1 parseBit '0' = Right Bit0 parseBit c = Left ("Invalid character: " <> show c) toBits :: FiniteBits a => a -> [Bit] toBits a = reverse $ map (\i -> bool Bit0 Bit1 (testBit a i)) [0..(n - 1)] where n = finiteBitSize a fromBits :: (Num a, Bits a) => [Bit] -> a fromBits s = foldl' f 0 (zip (reverse s) [0..]) where f accu (c, i) = case c of Bit1 -> setBit accu i Bit0 -> accu nthWord :: (Bits a, Integral a) => a -> Int -> Word8 nthWord = go where go a 0 = fromIntegral $ a .&. 0xFF go a i = go (a `shiftR` 8) (i - 1) genPosFixInt :: (MonadGen m, Integral a) => m a genPosFixInt = Gen.integral (Range.linear 0 (2^7 - 1)) genNegFixInt :: forall m a. (MonadGen m, Integral a) => m a genNegFixInt = fromIntegral . (fromIntegral :: a -> Int8) <$> Gen.integral (Range.linear (- 2^5) (- 1)) checkSerialization :: (Show a, Eq a, FromMsgPack a, ToMsgPack a) => a -> [Word8] -> PropertyT IO () checkSerialization a ws = do annotate $ show a size <- msgPackSize a let bsSerialized = runPacking size (toMsgPack a) bsExpected = ByteString.pack ws annotate . show $ (ByteString.length bsSerialized) annotate . show $ (ByteString.length bsExpected) ByteString.unpack bsSerialized === ByteString.unpack bsExpected annotate $ show bsSerialized a === runUnpacking fromMsgPack bsSerialized extractWordsBE :: forall a. (Integral a, FiniteBits a) => a -> [Word8] extractWordsBE = go nWords [] where nWords = (finiteBitSize (undefined :: a) + 7) `div` 8 go 0 accu _ = accu go i accu a = let a' = fromIntegral (a `shiftR` 8) in go (i - 1) (fromIntegral (a .&. 0xFF) : accu) a' -- Nil prop_nil :: Property prop_nil = property $ do nil <- forAll $ Gen.constant ObjectNil checkSerialization nil [0xC0] -- Bool prop_bool :: Property prop_bool = property $ do b <- forAll $ Gen.bool checkSerialization b [if b then 0xC3 else 0xC2] prop_obj_bool :: Property prop_obj_bool = property $ do b <- forAll Gen.bool checkSerialization (ObjectBool b) [if b then 0xC3 else 0xC2] _prop_pos_fixnum_integral :: forall a. (Show a, FromMsgPack a, ToMsgPack a, Integral a) => Proxy a -> PropertyT IO () _prop_pos_fixnum_integral _proxy = do a :: a <- forAll genPosFixInt checkSerialization a [fromIntegral a .&. 0b01111111] _prop_obj_pos_fixnum_integral :: forall a. (Show a, FromMsgPack a, ToMsgPack a, Integral a) => Proxy a -> PropertyT IO () _prop_obj_pos_fixnum_integral _proxy = do a :: a <- forAll genPosFixInt checkSerialization (ObjectInt (fromIntegral a)) [fromIntegral a .&. 0b01111111] prop_pos_fixnum_int8 :: Property prop_pos_fixnum_int8 = property $ _prop_pos_fixnum_integral (Proxy :: Proxy Int8) prop_obj_pos_fixnum_int8 :: Property prop_obj_pos_fixnum_int8 = property $ _prop_obj_pos_fixnum_integral (Proxy :: Proxy Int8) prop_pos_fixnum_int16 :: Property prop_pos_fixnum_int16 = property $ _prop_pos_fixnum_integral (Proxy :: Proxy Int16) prop_obj_pos_fixnum_int16 :: Property prop_obj_pos_fixnum_int16 = property $ _prop_obj_pos_fixnum_integral (Proxy :: Proxy Int16) prop_pos_fixnum_int32 :: Property prop_pos_fixnum_int32 = property $ _prop_pos_fixnum_integral (Proxy :: Proxy Int32) prop_obj_pos_fixnum_int32 :: Property prop_obj_pos_fixnum_int32 = property $ _prop_obj_pos_fixnum_integral (Proxy :: Proxy Int32) prop_pos_fixnum_int64 :: Property prop_pos_fixnum_int64 = property $ _prop_pos_fixnum_integral (Proxy :: Proxy Int64) prop_obj_pos_fixnum_int64 :: Property prop_obj_pos_fixnum_int64 = property $ _prop_obj_pos_fixnum_integral (Proxy :: Proxy Int64) prop_obj_pos_fixnum_word8 :: Property prop_obj_pos_fixnum_word8 = property $ _prop_obj_pos_fixnum_integral (Proxy :: Proxy Word8) prop_pos_fixnum_word16 :: Property prop_pos_fixnum_word16 = property $ _prop_pos_fixnum_integral (Proxy :: Proxy Word16) prop_obj_pos_fixnum_word16 :: Property prop_obj_pos_fixnum_word16 = property $ _prop_obj_pos_fixnum_integral (Proxy :: Proxy Word16) prop_pos_fixnum_word32 :: Property prop_pos_fixnum_word32 = property $ _prop_pos_fixnum_integral (Proxy :: Proxy Word32) prop_obj_pos_fixnum_word32 :: Property prop_obj_pos_fixnum_word32 = property $ _prop_obj_pos_fixnum_integral (Proxy :: Proxy Word32) prop_pos_fixnum_word64 :: Property prop_pos_fixnum_word64 = property $ _prop_pos_fixnum_integral (Proxy :: Proxy Word64) prop_obj_pos_fixnum_word64 :: Property prop_obj_pos_fixnum_word64 = property $ _prop_obj_pos_fixnum_integral (Proxy :: Proxy Word64) _prop_neg_fixnum_integral :: forall a. (Show a, FromMsgPack a, ToMsgPack a, Integral a) => Proxy a -> PropertyT IO () _prop_neg_fixnum_integral _proxy = do a :: a <- forAll genNegFixInt checkSerialization a [fromIntegral a .&. 0b11111111] _prop_obj_neg_fixnum_integral :: forall a. (Show a, FromMsgPack a, ToMsgPack a, Integral a) => Proxy a -> PropertyT IO () _prop_obj_neg_fixnum_integral _proxy = do a :: a <- forAll genNegFixInt checkSerialization (ObjectInt (fromIntegral a)) [fromIntegral a .&. 0b11111111] prop_neg_fixnum_int8 :: Property prop_neg_fixnum_int8 = property $ _prop_neg_fixnum_integral (Proxy :: Proxy Int8) prop_obj_neg_fixnum_int8 :: Property prop_obj_neg_fixnum_int8 = property $ _prop_obj_neg_fixnum_integral (Proxy :: Proxy Int8) prop_neg_fixnum_int16 :: Property prop_neg_fixnum_int16 = property $ _prop_neg_fixnum_integral (Proxy :: Proxy Int16) prop_obj_neg_fixnum_int16 :: Property prop_obj_neg_fixnum_int16 = property $ _prop_obj_neg_fixnum_integral (Proxy :: Proxy Int16) prop_neg_fixnum_int32 :: Property prop_neg_fixnum_int32 = property $ _prop_neg_fixnum_integral (Proxy :: Proxy Int32) prop_obj_neg_fixnum_int32 :: Property prop_obj_neg_fixnum_int32 = property $ _prop_obj_neg_fixnum_integral (Proxy :: Proxy Int32) prop_neg_fixnum_int64 :: Property prop_neg_fixnum_int64 = property $ _prop_neg_fixnum_integral (Proxy :: Proxy Int64) prop_obj_neg_fixnum_int64 :: Property prop_obj_neg_fixnum_int64 = property $ _prop_obj_neg_fixnum_integral (Proxy :: Proxy Int64) genWord8 :: forall m a. (MonadGen m, Integral a) => m a genWord8 = fromIntegral . (fromIntegral :: a -> Word8) <$> Gen.integral (Range.linear (2^7) (2^8 - 1)) prop_word8_word8 :: Property prop_word8_word8 = property $ do i :: Word8 <- forAll genWord8 checkSerialization i [0xCC, fromIntegral i] prop_obj_word8 :: Property prop_obj_word8 = property $ do i <- forAll genWord8 checkSerialization (ObjectUInt i) [0xCC, fromIntegral i] prop_word8_word16 :: Property prop_word8_word16 = property $ do i :: Word16 <- forAll genWord8 checkSerialization i [0xCC, fromIntegral i] prop_word8_word32 :: Property prop_word8_word32 = property $ do i :: Word32 <- forAll genWord8 checkSerialization i [0xCC, fromIntegral i] prop_word8_word64 :: Property prop_word8_word64 = property $ do i :: Word64 <- forAll genWord8 checkSerialization i [0xCC, fromIntegral i] -- UInt16 genWord16 :: forall m a. (MonadGen m, Integral a) => m a genWord16 = fromIntegral . (fromIntegral :: a -> Word16) <$> Gen.integral (Range.linear (2^8) (2^16 - 1)) prop_word16_word16 :: Property prop_word16_word16 = property $ do i :: Word16 <- forAll genWord16 checkSerialization i [0xCD, nthWord i 1, nthWord i 0] prop_obj_word16 :: Property prop_obj_word16 = property $ do i :: Word16 <- forAll genWord16 checkSerialization (ObjectUInt (fromIntegral i)) (0xCD : extractWordsBE i) prop_word16_word32 :: Property prop_word16_word32 = property $ do i :: Word16 <- forAll genWord16 checkSerialization (fromIntegral i :: Word32) (0xCD : extractWordsBE i) prop_word16_word64 :: Property prop_word16_word64 = property $ do i :: Word64 <- forAll genWord16 checkSerialization i [0xCD, nthWord i 1, nthWord i 0] -- UInt32 genWord32 :: forall m a. (MonadGen m, Integral a) => m a genWord32 = fromIntegral . (fromIntegral :: a -> Word32) <$> Gen.integral (Range.linear (2^16) (2^32 - 1)) prop_word32_word32 :: Property prop_word32_word32 = property $ do i :: Word32 <- forAll genWord32 checkSerialization i (0xCE : extractWordsBE i) prop_obj_word32 :: Property prop_obj_word32 = property $ do i :: Word32 <- forAll genWord32 checkSerialization (ObjectUInt (fromIntegral i)) (0xCE : extractWordsBE i) prop_word32_word64 :: Property prop_word32_word64 = property $ do i :: Word32 <- forAll genWord32 checkSerialization (fromIntegral i :: Word64) (0xCE : extractWordsBE i) -- UInt64 genWord64 :: forall m a. (MonadGen m, Integral a) => m a genWord64 = fromIntegral . (fromIntegral :: a -> Word64) <$> Gen.integral (Range.linear (2^32) (2^64 - 1)) prop_word64_word64 :: Property prop_word64_word64 = property $ do i :: Word64 <- forAll genWord64 checkSerialization i (0xCF : extractWordsBE i) prop_obj_word64 :: Property prop_obj_word64 = property $ do i :: Word64 <- forAll genWord64 checkSerialization (ObjectUInt i) (0xCF : extractWordsBE i) -- Int8 genInt8 :: forall m a. (MonadGen m, Integral a) => m a genInt8 = fromIntegral . (fromIntegral :: a -> Int8) <$> Gen.integral (Range.linear (-2^5 - 1) (-2^7)) prop_int8_int8 :: Property prop_int8_int8 = property $ do i :: Int8 <- forAll genInt8 checkSerialization i [0xD0, fromIntegral i] prop_obj_int8 :: Property prop_obj_int8 = property $ do i :: Int8 <- forAll genInt8 checkSerialization (ObjectInt (fromIntegral i)) (0xD0 : extractWordsBE i) prop_int8_int16 :: Property prop_int8_int16 = property $ do i :: Int8 <- forAll genInt8 checkSerialization (fromIntegral i :: Int16) (0xD0 : extractWordsBE i) prop_int8_int32 :: Property prop_int8_int32 = property $ do i :: Int8 <- forAll genInt8 checkSerialization (fromIntegral i :: Int32) (0xD0 : extractWordsBE i) prop_int8_int64 :: Property prop_int8_int64 = property $ do i :: Int8 <- forAll genInt8 checkSerialization (fromIntegral i :: Int64) (0xD0 : extractWordsBE i) -- Int prop_int8_int :: Property prop_int8_int = property $ do i :: Int8 <- forAll genInt8 checkSerialization (fromIntegral i :: Int) (0xD0 : extractWordsBE i) prop_int16_int :: Property prop_int16_int = property $ do i :: Int16 <- forAll genInt16 checkSerialization (fromIntegral i :: Int) (0xD1 : extractWordsBE i) -- Int16 genInt16 :: forall m a. (MonadGen m, Integral a) => m a genInt16 = fromIntegral . (fromIntegral :: a -> Int16) <$> Gen.choice [ Gen.integral (Range.linear (-2^15) (-2^ 7 - 1)) , Gen.integral (Range.linear ( 2^ 7) ( 2^15 - 1)) ] prop_int16_int16 :: Property prop_int16_int16 = property $ do i :: Int16 <- forAll genInt16 checkSerialization i (0xD1 : extractWordsBE i) prop_obj_int16 :: Property prop_obj_int16 = property $ do i :: Int16 <- forAll genInt16 checkSerialization (ObjectInt (fromIntegral i)) (0xD1 : extractWordsBE i) prop_int16_int32 :: Property prop_int16_int32 = property $ do i :: Int32 <- forAll genInt16 checkSerialization i (0xD1 : extractWordsBE (fromIntegral i :: Int16)) prop_int16_int64 :: Property prop_int16_int64 = property $ do i :: Int64 <- forAll genInt16 checkSerialization i (0xD1 : extractWordsBE (fromIntegral i :: Int16)) -- Int32 genInt32 :: forall m a. (MonadGen m, Integral a) => m a genInt32 = fromIntegral . (fromIntegral :: a -> Int32) <$> Gen.choice [ Gen.integral (Range.linear (-2^31) (-2^15 - 1)) , Gen.integral (Range.linear ( 2^15) ( 2^31 - 1))] prop_int32_int32 :: Property prop_int32_int32 = property $ do i :: Int32 <- forAll genInt32 checkSerialization i (0xD2 : extractWordsBE (fromIntegral i :: Int32)) prop_obj_int32 :: Property prop_obj_int32 = property $ do i :: Int32 <- forAll genInt32 checkSerialization (ObjectInt (fromIntegral i)) (0xD2 : extractWordsBE i) prop_int32_int64 :: Property prop_int32_int64 = property $ do i :: Int64 <- forAll genInt32 checkSerialization i (0xD2 : extractWordsBE (fromIntegral i :: Int32)) -- Int64 genInt64 :: forall m a. (MonadGen m, Integral a) => m a genInt64 = fromIntegral . (fromIntegral :: a -> Int64) <$> Gen.choice [ Gen.integral (Range.linear (-2^63) (-2^31 - 1)) , Gen.integral (Range.linear ( 2^31) ( 2^63 - 1))] prop_int64_int64 :: Property prop_int64_int64 = property $ do i :: Int64 <- forAll genInt64 checkSerialization i (0xD3 : extractWordsBE (fromIntegral i :: Int64)) prop_obj_int64 :: Property prop_obj_int64 = property $ do i :: Int64 <- forAll genInt64 checkSerialization i (0xD3 : extractWordsBE i) -- Float32 genFloat32 :: MonadGen m => m Float genFloat32 = Gen.float (Range.exponentialFloat (- 2^32) (2^32)) prop_float32 :: Property prop_float32 = property $ do f <- forAll genFloat32 checkSerialization f (0xCA : ByteString.unpack (runPacking 16 (putFloat32BE f))) floatWordsBE :: Float -> [Word8] floatWordsBE f = ByteString.unpack (runPacking 16 (putFloat32BE f)) doubleWordsBE :: Double -> [Word8] doubleWordsBE f = ByteString.unpack (runPacking 16 (putFloat64BE f)) prop_obj_float32 :: Property prop_obj_float32 = property $ do f <- forAll genFloat32 checkSerialization (ObjectFloat32 f) (0xCA : floatWordsBE f) -- Float64 genFloat64 :: MonadGen m => m Double genFloat64 = Gen.double (Range.exponentialFloat (- 2^64) (2^64)) prop_float64 :: Property prop_float64 = property $ do f <- forAll genFloat64 checkSerialization f (0xCB : ByteString.unpack (runPacking 16 (putFloat64BE f))) prop_obj_float64 :: Property prop_obj_float64 = property $ do f <- forAll genFloat64 checkSerialization (ObjectFloat64 f) (0xCB : doubleWordsBE f) -- Produces much more ASCII characters than unicode in order to -- increase the change that generated texts will have a UTF decoded -- bytestring in the requested range. genChar :: MonadGen m => m Char genChar = Gen.frequency [ (1, Gen.unicode) , (9, Gen.ascii) ] genStr :: MonadGen m => Range Int -> m Text genStr range = Gen.filter (decodedTextInRange range) (Gen.text range genChar) decodedTextInRange :: Range Int -> Text -> Bool decodedTextInRange range text = let l = ByteString.length (Text.encodeUtf8 text) lower = Range.lowerBound 100 range upper = Range.upperBound 100 range in lower <= l && l <= upper decodeTextLength :: Integral a => Text -> (ByteString, a) decodeTextLength t = let bs = Text.encodeUtf8 t n = fromIntegral $ ByteString.length bs in (bs, n) -- FixStr genFixStr :: MonadGen m => m Text genFixStr = genStr $ Range.linear 0 (2^5 - 1) prop_fixstr :: Property prop_fixstr = property $ do t <- forAll genFixStr let (bs, n :: Word8) = decodeTextLength t checkSerialization t $ (fromIntegral n .|. 0b10100000) : ByteString.unpack bs prop_obj_fixstr :: Property prop_obj_fixstr = property $ do t <- forAll genFixStr let (bs, n :: Word8) = decodeTextLength t checkSerialization (ObjectString t) $ (fromIntegral n .|. 0b10100000) : ByteString.unpack bs -- Str8 genStr8 :: MonadGen m => m Text genStr8 = genStr $ Range.linear (2^5) (2^8 - 1) prop_str8 :: Property prop_str8 = property $ do s <- forAll genStr8 let (bs, l :: Word8) = decodeTextLength s checkSerialization s (0xD9 : (extractWordsBE l ++ ByteString.unpack bs)) prop_obj_str8 :: Property prop_obj_str8 = property $ do s <- forAll genStr8 let (bs, l :: Word8) = decodeTextLength s checkSerialization (ObjectString s) (0xD9 : (extractWordsBE l ++ ByteString.unpack bs)) -- Str16 genStr16 :: MonadGen m => m Text genStr16 = genStr $ Range.linear (2^8) (2^9 - 1) -- 2^16 - 1 would be too slow. prop_str16 :: Property prop_str16 = property $ do s <- forAll genStr16 let (bs, l :: Word16) = decodeTextLength s checkSerialization s (0xDA : (extractWordsBE l ++ ByteString.unpack bs)) prop_obj_str16 :: Property prop_obj_str16 = property $ do s <- forAll genStr16 let (bs, l :: Word16) = decodeTextLength s checkSerialization (ObjectString s) (0xDA : (extractWordsBE l ++ ByteString.unpack bs)) -- Bin8 genBin8 :: MonadGen m => m ByteString genBin8 = Gen.bytes $ Range.linear 0 (2^8 - 1) prop_bin8 :: Property prop_bin8 = property $ do b <- forAll genBin8 let l = fromIntegral (ByteString.length b) :: Word8 checkSerialization b (0xC4 : (extractWordsBE l ++ ByteString.unpack b)) prop_obj_bin8 :: Property prop_obj_bin8 = property $ do b <- forAll genBin8 let l = fromIntegral (ByteString.length b) :: Word8 checkSerialization (ObjectBinary b) (0xC4 : (extractWordsBE l ++ ByteString.unpack b)) -- Bin16 genBin16 :: MonadGen m => m ByteString genBin16 = Gen.bytes $ Range.linear (2^8) (2^9 - 1) -- 2^16 - 1 would be too slow. prop_bin16 :: Property prop_bin16 = property $ do b <- forAll genBin16 let l = fromIntegral (ByteString.length b) :: Word16 checkSerialization b (0xC5 : (extractWordsBE l ++ ByteString.unpack b)) prop_obj_bin16 :: Property prop_obj_bin16 = property $ do b <- forAll genBin16 let l = fromIntegral (ByteString.length b) :: Word16 checkSerialization (ObjectBinary b) (0xC5 : (extractWordsBE l ++ ByteString.unpack b)) genObject :: MonadGen m => m Object genObject = Gen.frequency [ (1, ObjectBinary <$> genBin8) , (1, pure ObjectNil) , (1, ObjectBool <$> Gen.bool) , (1, ObjectString <$> genFixStr) , (1, ObjectString <$> genStr8) , (1, ObjectBinary <$> genBin8) , (1, ObjectInt <$> genInt16) , (1, ObjectUInt <$> genWord16) ] genObjectWithSize :: MonadGen m => m (Maybe (Object, Int)) genObjectWithSize = do obj <- genObject case msgPackSize obj of Just size -> return $ Just (obj, size) Nothing -> return Nothing serializeObj :: Object -> Int -> ByteString serializeObj obj size = runPacking size (toMsgPack obj) genObjectPairWithSize :: MonadGen m => m (Maybe ((Object, Int), (Object, Int))) genObjectPairWithSize = do a <- genObjectWithSize b <- genObjectWithSize return $ (,) <$> a <*> b prop_array :: Property prop_array = property $ do objsWithSize <- catMaybes <$> (forAll $ Gen.list (Range.linear (2^4) (2^6)) genObjectWithSize) let objs = map fst objsWithSize n = fromIntegral (length objs) :: Int16 objsSerialized = ByteString.unpack . mconcat . map (uncurry serializeObj) $ objsWithSize checkSerialization (ObjectArray objs) (0xDC : (extractWordsBE n ++ objsSerialized)) serializeObjPair :: (Object, Int) -> (Object, Int) -> ByteString serializeObjPair (obj0, size0) (obj1, size1) = runPacking (fromIntegral (size0 + size1)) (toMsgPack obj0 >> toMsgPack obj1) filterDuplicates :: (Ord a, Ord b) => [(a, b)] -> [(a, b)] filterDuplicates = Map.toList . Map.fromList prop_fixmap :: Property prop_fixmap = property $ do objsWithSize <- filterDuplicates . catMaybes <$> (forAll $ Gen.list (Range.linear 0 15) genObjectPairWithSize) let objPairs = map (\(a, b) -> (fst a, fst b)) objsWithSize n = fromIntegral (length objPairs) :: Int16 objsSerialized = ByteString.unpack . mconcat . map (uncurry serializeObjPair) $ objsWithSize annotate . show $ n checkSerialization (ObjectMap (Map.fromList objPairs)) ((0b10000000 .|. (fromIntegral n)) : objsSerialized) tests :: IO Bool tests = checkParallel $$(discover) main :: IO () main = tests >>= \case True -> putStrLn "Tests Passed" False -> putStrLn "Tests Failed"