ipld-cid-0.1.0.0: test/properties/Main.hs
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE TemplateHaskell #-}
{-# LANGUAGE TypeApplications #-}
module Main where
import Control.Monad (unless)
import qualified Crypto.Hash as C
import Data.ByteString (ByteString)
import qualified Data.ByteString.BaseN as BaseN
import qualified Data.ByteString.Builder as Builder
import qualified Data.ByteString.Lazy as LBS
import Data.Foldable (for_)
import Data.Functor.Identity (Identity)
import Data.List (isInfixOf)
import Data.Text.Encoding (decodeUtf8)
import System.Exit (exitFailure)
import System.IO (BufferMode(..), hSetBuffering, stderr, stdout)
import Data.ByteString.Multibase (fromMultibase)
import qualified Data.ByteString.Multibase as Multibase
import Data.IPLD.CID
( CID
, buildCid
, cidFromText
, cidToText
, newCidV0
, newCidV1
)
import qualified Data.IPLD.CID as CID
import Data.Multihash (Multihash, Multihashable)
import qualified Data.Multihash as Multihash
import Data.Multihash.Internal (HashAlgorithm(..))
import Hedgehog
import qualified Hedgehog.Gen as Gen
import qualified Hedgehog.Range as Range
main :: IO ()
main = do
for_ [stdout, stderr] $ flip hSetBuffering LineBuffering
ok <- props
unless ok exitFailure
props :: IO Bool
props = checkParallel $$(discover)
prop_roundtripBytes :: Property
prop_roundtripBytes = property $ do
cid <- forAll genCID
tripping cid (Builder.toLazyByteString . buildCid) (CID.decodeCid . LBS.toStrict)
prop_roundtripText :: Property
prop_roundtripText = property $ do
cid <- forAll genCID
tripping cid cidToText cidFromText
-- | "If the first decoded byte is 0x12, return an error. CIDv0 CIDs may not be
-- multibase encoded and there will be no CIDv18 (0x12 = 18) to prevent
-- ambiguity with decoded CIDv0s."
--
-- It seems like the only way this can happen is to pass a 'C.SHA256'
-- 'Multihash' (or an arbitrary byte sequence which happens to start with 0x12),
-- encoded at some base /= 'Base58btc'. ie. the multibase encoding has to be
-- valid.
prop_ambiguousVersion :: Property
prop_ambiguousVersion = property $ do
hash <- forAllWith (show . Multihash.encodedBytes) (genMultihash C.SHA256)
let
encoded = Multibase.encode (BaseN.encodeAtBase BaseN.Base32 . Multihash.encodedBytes $ hash)
notACid = decodeUtf8 $ fromMultibase encoded
fromTxt = cidFromText notACid
in do
annotate (show $ fromMultibase encoded)
annotateShow notACid
annotateShow fromTxt
case fromTxt of
Left e | "reserved" `isInfixOf` e -> success
_ -> failure
-- Generators ------------------------------------------------------------------
genCID :: GenT Identity CID
genCID = Gen.choice [v0, v1]
where
v0 = newCidV0 <$> genDigest
v1 = do
codec <- genCodec
algo <- genHashAlgorithm
case algo of
Blake2s_160 -> newCidV1 codec <$> genDigest @C.Blake2s_160
Blake2s_224 -> newCidV1 codec <$> genDigest @C.Blake2s_224
Blake2s_256 -> newCidV1 codec <$> genDigest @C.Blake2s_256
Blake2b_160 -> newCidV1 codec <$> genDigest @C.Blake2b_160
Blake2b_224 -> newCidV1 codec <$> genDigest @C.Blake2b_224
Blake2b_256 -> newCidV1 codec <$> genDigest @C.Blake2b_256
Blake2b_384 -> newCidV1 codec <$> genDigest @C.Blake2b_384
Blake2b_512 -> newCidV1 codec <$> genDigest @C.Blake2b_512
MD4 -> newCidV1 codec <$> genDigest @C.MD4
MD5 -> newCidV1 codec <$> genDigest @C.MD5
SHA1 -> newCidV1 codec <$> genDigest @C.SHA1
SHA256 -> newCidV1 codec <$> genDigest @C.SHA256
SHA512 -> newCidV1 codec <$> genDigest @C.SHA512
Keccak_224 -> newCidV1 codec <$> genDigest @C.Keccak_224
Keccak_256 -> newCidV1 codec <$> genDigest @C.Keccak_256
Keccak_384 -> newCidV1 codec <$> genDigest @C.Keccak_384
Keccak_512 -> newCidV1 codec <$> genDigest @C.Keccak_512
SHA3_224 -> newCidV1 codec <$> genDigest @C.SHA3_224
SHA3_256 -> newCidV1 codec <$> genDigest @C.SHA3_256
SHA3_384 -> newCidV1 codec <$> genDigest @C.SHA3_384
SHA3_512 -> newCidV1 codec <$> genDigest @C.SHA3_512
genMultihash :: forall a. Multihashable a => a -> GenT Identity Multihash
genMultihash _ = Multihash.fromDigest <$> genDigest @a
genDigest :: Multihashable a => GenT Identity (C.Digest a)
genDigest = C.hash <$> genBytes
genHashAlgorithm :: GenT Identity Multihash.HashAlgorithm
genHashAlgorithm = Gen.prune Gen.enumBounded
genBytes :: GenT Identity ByteString
genBytes = Gen.prune $ Gen.bytes (Range.singleton 255)
genCodec :: GenT Identity CID.Codec
genCodec = Gen.element [CID.Raw, CID.DagProtobuf, CID.DagCbor, CID.GitRaw]